5.5 结构体与联合体

本节描述SWIG处理ANSI C结构体和联合体声明相关的行为。对处理C++声明的处理在下一章讲述。

当SWIG遇到结构体或联合体的定义时,它会创建一组访问函数。尽管SWIG不需要结构体的定义去定义接口,提供这些定义让它可以访问结构体的成员。SWIG生成的访问函数简单的拥有一个指向该对象的指针,并且允许访问私有成员。例如,下面的声明:

  1. struct Vector {
  2. double x,y,z;
  3. }

将转换成下面一组访问函数:

  1. double Vector_x_get(struct Vector *obj) {
  2. return obj->x;
  3. }
  4. double Vector_y_get(struct Vector *obj) {
  5. return obj->y;
  6. }
  7. double Vector_z_get(struct Vector *obj) {
  8. return obj->z;
  9. }
  10. void Vector_x_set(struct Vector *obj, double value) {
  11. obj->x = value;
  12. }
  13. void Vector_y_set(struct Vector *obj, double value) {
  14. obj->y = value;
  15. }
  16. void Vector_z_set(struct Vector *obj, double value) {
  17. obj->z = value;
  18. }

除此之外,如果没在接口文件中定义的话,SWIG还会创建默认的构造函数和析构函数。如:

  1. struct Vector *new_Vector() {
  2. return (Vector *) calloc(1,sizeof(struct Vector));
  3. }
  4. void delete_Vector(struct Vector *obj) {
  5. free(obj);
  6. }

使用这些底层的访问函数,可以从目标语言中这样访问一个对象:

  1. v = new_Vector()
  2. Vector_x_set(v,2)
  3. Vector_y_set(v,10)
  4. Vector_z_set(v,-5)
  5. ...
  6. delete_Vector(v)

但是,多数的SWIG语言模块同时也提供更高层次的接口,使用它们更便利。接着往下读。

5.5.1 Typedef与structures

SWIG支持下面这样的在C中非常普遍的构造:

  1. typedef struct {
  2. double x,y,z;
  3. } Vector;

当遇到这样的情况时,SWIG假设对象的名字是Vector,并像前面描述的一样创建访问函数。唯一的不同是,使用typedef允许SWIG在生成的代码中丢掉struct关键字。如:

  1. double Vector_x_get(Vector *obj) {
  2. return obj->x;
  3. }

如果像下面这样使用了不同的名字:

  1. typedef struct vector_struct {
  2. double x,y,z;
  3. } Vector

就会使用名字Vector而不是vector_struct,因为这才是更经典的C语言编程风格。如果,后来在接口文件中使用了类型struct vector_struct,SWIG就会知道这与Vector是一样的,它会生成合适的类型检查代码。

5.5.2 字符串与结构体

包含字符串的结构体需要特别注意。SWIG假设所有的char*类型都使用malloc()函数动态申请,并且都是以NULL结束的ASCII字符串。当这样的成员被修改时,先前的内容将会被释放,新内容将重新申请。如:

  1. %module mymodule
  2. ...
  3. struct Foo {
  4. char *name;
  5. ...
  6. }

会创建如下的访问函数:

  1. char *Foo_name_get(Foo *obj) {
  2. return Foo->name;
  3. }
  4. char *Foo_name_set(Foo *obj, char *c) {
  5. if (obj->name) free(obj->name);
  6. obj->name = (char *) malloc(strlen(c)+1);
  7. strcpy(obj->name,c);
  8. return obj->name;
  9. }

如果你的程序行为不是这样的,SWIG的typemap memberin可用于改变其行为。查看typemap相关章节了解更多信息。

注意:如果使用了-C++选项,newdelete会被用于执行内存分配。

5.5.2 数组成员

数组可以作为结构体的成员,但它们将变成只读的。SWIG将会创建一个访问函数,返回指向第一个数组元素地址的指针,但不会创建改变数组内容的函数。当遇到这样的情况时,SWIG会生成一个像下面这样的警告消息:

  1. interface.i:116. Warning. Array member will be read-only

可以使用typemap消除这样的警告,这会在后面的章节介绍。多数情况下,这些警告消息没什么害处。

5.5.3 结构体作为数据成员

偶尔,结构体也会包含结构体成员。如:

  1. typedef struct Foo {
  2. int x;
  3. } Foo;
  4. typedef struct Bar {
  5. int y;
  6. Foo f; /* struct member */
  7. } Bar;

当结构体成员被包装时,它被处理成指针,当时用%naturalvar指令后,它被处理成C++的引用。访问成员变量指针的函数包装成如下形式:

  1. Foo *Bar_f_get(Bar *b) {
  2. return &b->f;
  3. }
  4. void Bar_f_set(Bar *b, Foo *value) {
  5. b->f = *value;
  6. }

这样做的原因有些微妙,但这样做是为了解决访问和修改内部数据。例如,假设你想修改Bar对象的f.x的值:

  1. Bar *b;
  2. b->f.x = 37;

在脚本语言接口文件中,将其转换成赋值函数调用:

  1. Bar *b;
  2. Foo_x_set(Bar_f_get(b),37);

在这个代码中,如果Bar_f_get()函数返回Foo而不是Foo*的话,结果会导致只对f的拷贝进行了修改,结构体的成员f并没被修改。很明显,这不是你想要的结果!

需要注意的是,只有SWIG知道数据成员是结构体或类的时候才会做这样的转换。例如,如果有下面这样的结构体:

  1. struct Foo {
  2. WORD w;
  3. };

不知道WORD是什么类型,这是SWIG将会生成更通用的访问函数:

  1. WORD Foo_w_get(Foo *f) {
  2. return f->w;
  3. }
  4. void Foo_w_set(FOO *f, WORD value) {
  5. f->w = value;
  6. }

兼容性注释:SWIG-1.3.11和早期的发行版将所有的非原生数据类型都当做指针进行转换。从SWIG-1.2.12开始,只有当数据类型明确是结构体、类或联合体的时候才会转换。这不太可能破坏现有的代码。但是,如果你需要告诉SWIG一个为定义的数据类型真的是一个结果体的话,简单的使用前置结构体声明struct Foo;就行了。

5.5.5 C构造器与析构器

当包装结构体时,通常创建和删除对象的机制一般会非常有用。如果你没有这样做,SWIG将会使用mallocfree自动生成创建和删除对象的函数。需要注意的是,在C代码中使用malloc,C++代码中使用new

如果你不想让SWIG生成默认的构造函数,可以使用%nodefaultctor指令或-nodefaultctor命令行选项。如:

  1. swig -nodefaultctor example.i

或:

  1. %module foo
  2. ...
  3. %nodefaultctor; // Don't create default constructors
  4. ... declarations ...
  5. %clearnodefaultctor; // Re-enable default constructors

如果你想精确控制,%nodefaultctor可用来选择单独的结构体定义。如:

  1. %nodefaultctor Foo; // No default constructor for Foo
  2. ...
  3. struct Foo { // No default constructor generated.
  4. };
  5. struct Bar { // Default constructor generated.
  6. };

因为忽略隐式的或默认的析构函数多数情况下会导致内存泄露,SWIG总是会生成它们。但是,如果需要的话,你可以使用%nodefaultctor有选择性的禁止生成默认/隐式的析构函数。

兼容性注释:SWIG-1.3.7版之前,SWIG不会自动生成默认的构造函数和析构函数,除非你显式地使用-make_default选项打开它。但是,大部分的用户想要这样的特性,所以现在它是SWIG的默认行为。

注意:可以使用-nodefault命令行选项和%nodefault指令,禁止默认或隐式的析构函数。这会导致语言间相互调用后存在内存泄露,所以强烈建议你不要使用它们。

5.5.6 向C结构体天剑成员函数

多数语言都提供创建类和支持面向对象编程的机制。从C语言的观点看,面向对象编程其实可以归结为给结构体关联上函数。这些函数一般对结构体的实例(对象)进行操作。尽管使用C++的方案更自然,但在C语言中没有直接实现这些特性的机制。但是,SWIG提供了特殊的%extend指令,使用它就可以关联方法到C结构体上,从而构建面向对象的接口。假设你有如下声明的C语言头文件:

  1. /* file : vector.h */
  2. ...
  3. typedef struct Vector {
  4. double x,y,z;
  5. } Vector;

你可以在SWIG接口文件中这样写,让Vector看起来更像一个类:

  1. %module mymodule
  2. %{
  3. #include "vector.h"
  4. %}
  5. %include "vector.h" // Just grab original C header file
  6. %extend Vector { // Attach these functions to struct Vector
  7. Vector(double x, double y, double z) {
  8. Vector *v;
  9. v = (Vector *) malloc(sizeof(Vector));
  10. v->x = x;
  11. v->y = y;
  12. v->z = z;
  13. return v;
  14. }
  15. ~Vector() {
  16. free($self);
  17. }
  18. double magnitude() {
  19. return sqrt($self->x*$self->x+$self->y*$self->y+$self->z*$self->z);
  20. }
  21. void print() {
  22. printf("Vector [%g, %g, %g]\n", $self->x,$self->y,$self->z);
  23. }
  24. };

注意,$self特殊变量的使用。它和C++语言的this指针的用法是一样的,可用在访问结构体实例的任何地方。同时还要注意,即使是C代码也使用了C++语言的构造函数和析构函数的语法来模拟构造器与析构器。尽管使用了通常的C++构造函数,也与普通的C++构造函数实现由稍许区别,新的被构造出来的对象必须返回对象,在这个例子中就是Vector*

现在,当在Python中使用代理类,可以这么做:

  1. >>> v = Vector(3,4,0) # Create a new vector
  2. >>> print v.magnitude() # Print magnitude
  3. 5.0
  4. >>> v.print() # Print it out
  5. [ 3, 4, 0 ]
  6. >>> del v # Destroy it

%extend指令还可以用在Vector结构体的定义中。如:

  1. // file : vector.i
  2. %module mymodule
  3. %{
  4. #include "vector.h"
  5. %}
  6. typedef struct Vector {
  7. double x,y,z;
  8. %extend {
  9. Vector(double x, double y, double z) { ... }
  10. ~Vector() { ... }
  11. ...
  12. }
  13. } Vector;

注意,通过提供特定命名规则的函数,%extend还可以访问外部提供的函数:

  1. /* File : vector.c */
  2. /* Vector methods */
  3. #include "vector.h"
  4. Vector *new_Vector(double x, double y, double z) {
  5. Vector *v;
  6. v = (Vector *) malloc(sizeof(Vector));
  7. v->x = x;
  8. v->y = y;
  9. v->z = z;
  10. return v;
  11. }
  12. void delete_Vector(Vector *v) {
  13. free(v);
  14. }
  15. double Vector_magnitude(Vector *v) {
  16. return sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
  17. }
  1. // File : vector.i
  2. // Interface file
  3. %module mymodule
  4. %{
  5. #include "vector.h"
  6. %}
  7. typedef struct Vector {
  8. double x,y,z;
  9. %extend {
  10. Vector(int,int,int); // This calls new_Vector()
  11. ~Vector(); // This calls delete_Vector()
  12. double magnitude(); // This will call Vector_magnitude()
  13. ...
  14. }
  15. } Vector;

%extend使用的名字必须是原始结构体的名字而不能是用typedef定义的别名,如:

  1. typedef struct Integer {
  2. int value;
  3. } Int;
  4. %extend Integer { ... } /* Correct name */
  5. %extend Int { ... } /* Incorrect name */
  6. struct Float {
  7. float value;
  8. };
  9. typedef struct Float FloatValue;
  10. %extend Float { ... } /* Correct name */
  11. %extend FloatValue { ... } /* Incorrect name */

本规则有一个例外,当结构体以匿名方式命名后,如:

  1. typedef struct {
  2. double value;
  3. } Double;
  4. %extend Double { ... } /* Okay */

%extend有一个鲜为人知的特征,它还可以用于对存在的数据属性添加同步属性或修改其行为。例如,建设你想让Vectormagnitude变成只读的方法,可以这么做:

  1. // Add a new attribute to Vector
  2. %extend Vector {
  3. const double magnitude;
  4. }
  5. // Now supply the implementation of the Vector_magnitude_get function
  6. %{
  7. const double Vector_magnitude_get(Vector *v) {
  8. return (const double) sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
  9. }
  10. %}

现在,magnitude将变成对象的一个属性。

同样的技术还可以用于你想处理的数据成员。例如,考虑如下接口:

  1. typedef struct Person {
  2. char name[50];
  3. ...
  4. } Person;

如果你想让name是大写的,可以像下面这样重写接口,确保无论何时它被读或写都能达此目的:

  1. typedef struct Person {
  2. %extend {
  3. char name[50];
  4. }
  5. ...
  6. } Person;
  7. %{
  8. #include <string.h>
  9. #include <ctype.h>
  10. void make_upper(char *name) {
  11. char *c;
  12. for (c = name; *c; ++c)
  13. *c = (char)toupper((int)*c);
  14. }
  15. /* Specific implementation of set/get functions forcing capitalization */
  16. char *Person_name_get(Person *p) {
  17. make_upper(p->name);
  18. return p->name;
  19. }
  20. void Person_name_set(Person *p, char *val) {
  21. strncpy(p->name,val,50);
  22. make_upper(p->name);
  23. }
  24. %}

最后需要强调的是,即使%extend指令可以被用来添加数据成员,这个新添加的成员在对象分配额外的存储空间(如,它们的值必须完全从结构的现有属性或其他地方获得)。

兼容性注释:%extend指令是%addmethods指令的新名字。因为%addmethods不仅可以添加方法来扩展结构体,还可以做其他事情,所以更给它换了个更合适的名字。

5.5.7 内嵌的结构体

偶尔情况下,C程序中可能会包含向下面这样的结构体:

  1. typedef struct Object {
  2. int objtype;
  3. union {
  4. int ivalue;
  5. double dvalue;
  6. char *strvalue;
  7. void *ptrvalue;
  8. } intRep;
  9. } Object;

当SWIG遇到这种情况时,它执行结构拆分操作,将声明转换为等价的如下操作:

  1. typedef union {
  2. int ivalue;
  3. double dvalue;
  4. char *strvalue;
  5. void *ptrvalue;
  6. } Object_intRep;
  7. typedef struct Object {
  8. int objType;
  9. Object_intRep intRep;
  10. } Object;

SWIG会在接口文件中创建一个Object_intRep结构。同时,两个结构体的访问函数也会被创建。这种情况下,下面这些函数将会创建:

  1. Object_intRep *Object_intRep_get(Object *o) {
  2. return (Object_intRep *) &o->intRep;
  3. }
  4. int Object_intRep_ivalue_get(Object_intRep *o) {
  5. return o->ivalue;
  6. }
  7. int Object_intRep_ivalue_set(Object_intRep *o, int value) {
  8. return (o->ivalue = value);
  9. }
  10. double Object_intRep_dvalue_get(Object_intRep *o) {
  11. return o->dvalue;
  12. }
  13. ... etc ...

虽然这个过程有点毛毛糙糙的,但它在目标脚本语言中工作的与您在所期望的一样——特别是当时候代理类以后。例如,在Perl中:

  1. # Perl5 script for accessing nested member
  2. $o = CreateObject(); # Create an object somehow
  3. $o->{intRep}->{ivalue} = 7 # Change value of o.intRep.ivalue

如果你有很多的嵌入结果体声明,运行SWIG后,建议最好再检查一遍。尽管很有可能它们会工作,但在某些情况下,您可能需要修改接口文件。

最后,请注意在C++模式下嵌套处理的方式不同,请查看内嵌类

5.5.8 关于结构体包装的其他注意事项

SWIG不关心在.i后缀的接口文件中声明的结构体精确匹配C底层使用的代码(嵌套结构的情况除外)。因为这个原因,省略问题成员或者干脆省略结构定义是没有问题的。如果你愿意传递指针,可以不给出结构体的定义。

从SWIG 1.3开始,SWIG代码生成器做了很多改进。特别是,尽管结构体的访问被描述成高层次的访问函数,如:

  1. double Vector_x_get(Vector *v) {
  2. return v->x;
  3. }

但生成的包装代码本身却是内联的。因此,生成的包装代码中其实没有Vector_x_get()这个函数。例如,当创建Tcl模块是,下面的函数被生成:

  1. static int
  2. _wrap_Vector_x_get(ClientData clientData, Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[]) {
  3. struct Vector *arg1 ;
  4. double result ;
  5. if (SWIG_GetArgs(interp, objc, objv,"p:Vector_x_get self ",&arg0,SWIGTYPE_p_Vector)
  6. == TCL_ERROR)
  7. return TCL_ERROR;
  8. result = (double ) (arg1->x);
  9. Tcl_SetObjResult(interp,Tcl_NewDoubleObj((double) result));
  10. return TCL_OK;
  11. }

这个规则唯一例外是使用%extend指令定义的方法。这种情况下,添加的代码包含在一个单独的函数中。

最后,需要注意到的是,多数语言模块可以选择创建更多的高级接口,这很重要。尽管你可能从来不使用这里介绍的低级接口,但SWIG的语言模块在其他地方以另外的方式总在使用。