五、SWIG 之 结构体与共用体
/* example.i */ %module example %inline %{ struct Vector { double x, y, z; }; %}类似会变为以下一组访问函数:
double Vector_x_get(struct Vector *obj) { return obj->x; } double Vector_y_get(struct Vector *obj) { return obj->y; } double Vector_z_get(struct Vector *obj) { return obj->z; } void Vector_x_set(struct Vector *obj, double value) { obj->x = value; } void Vector_y_set(struct Vector *obj, double value) { obj->y = value; } void Vector_z_set(struct Vector *obj, double value) { obj->z = value; }此外,如果接口中没有定义,SWIG 会创建默认构造函数和析构函数。例如:
struct Vector *new_Vector() { return (Vector *) calloc(1, sizeof(struct Vector)); } void delete_Vector(struct Vector *obj) { free(obj); }
typedef struct { double x, y, z; } Vector;当遇到时,SWIG 假定对象的名称是 Vector 并创建像之前一样的访问器函数。唯一的区别是使用 typedef 允许 SWIG 在其生成的代码上删除 struct 关键字。例如:
double Vector_x_get(Vector *obj) { return obj->x; }如果两个不同的名字被如下使用:
typedef struct vector_struct { double x, y, z; } Vector;使用名称 Vector 代替 vector_struct,因为这是更典型的 C 编程风格。如果稍后在接口中定义的声明使用类型 struct vector_struct,则 SWIG 知道它与 Vector 相同,并且它生成适当的类型检查代码。
/* example.i */ %module example %inline %{ struct Foo { char *name; }; %}这导致了如下的访问器函数:
char *Foo_name_get(Foo *obj) { return Foo->name; } char *Foo_name_set(Foo *obj, char *c) { if (obj->name) free(obj->name); obj->name = (char *) malloc(strlen(c)+1); strcpy(obj->name, c); return obj->name; }如果此行为与你在应用程序中所需的行为不同,则可以使用 SWIG memberin 类型映射来更改它。
注意:如果使用 -c++ 选项,则使用 new 和 delete 来执行内存分配。
/* example.i */ %module example %inline %{ int array[10] = {0}; %}Warning. Array member will be read-only
要消除警告消息,可以使用类型映射,但这将在后面的章节中讨论。在许多情况下,警告消息是无害的。
/* example.i */ %module example %inline %{ typedef struct Foo { int x; } Foo; typedef struct Bar { int y; Foo f; /* struct member */ } Bar; %}当一个结构体成员被包装时,它被作为指针处理,除非使用 %naturalvar 指令处理它更像 C++ 引用。作为指针的成员变量的访问器函数有效地被包装成如下形式:
Foo *Bar_f_get(Bar *b) { return &b->f; } void Bar_f_set(Bar *b, Foo *value) { b->f = *value; }其原因有些微妙,但与修改和访问数据成员内部数据的问题有关。例如,假设你想要修改 Bar 对象的 f.x 的值,如下所示:
Bar *b; b->f.x = 37;将此赋值转换为函数调用(将在脚本语言接口中使用)会产生以下代码:
Bar *b; Foo_x_set(Bar_f_get(b), 37);在这段代码中,如果 Bar_f_get() 函数返回一个 Foo 而不是 Foo *,那么得到的修改将应用于 f 的副本而不是数据成员 f 本身。显然,这不是你想要的!
应该注意,只有当 SWIG 知道数据成员是结构体或类时,才会发生对指针的转换。例如,如果你有这样的结构,
struct Foo { WORD w; };并且对 WORD 一无所知,那么 SWIG 将生成更多正常的访问器函数,如下所示:
WORD Foo_w_get(Foo *f) { return f->w; } void Foo_w_set(FOO *f, WORD value) { f->w = value; }
在包装结构体时,通常有一种用于创建和销毁对象的机制。如果你什么都不做,SWIG 会自动生成使用 malloc() 和 free() 创建和销毁对象的函数。注意:使用 malloc() 仅适用于在 C 代码上使用 SWIG(即,在命令行上不提供 -c++ 选项)。C++ 的处理方式不同。
如果你不希望 SWIG 为你的接口生成默认构造函数,则可以使用 %nodefaultctor 指令或 -nodefaultctor 命令行选项。例如:
swig -nodefaultctor -python example.i
或者
/* example.i */ %module example %inline %{ typedef struct Foo { int x; } Foo; %nodefaultctor; // Don't create default constructors typedef struct Bar { int y; Foo f; /* struct member */ } Bar; %clearnodefaultctor; // Re-enable default constructors %}如果你需要更精确的控制,%nodefaultctor 可以选择性地定位单个结构体定义。例如:
/* example.i */ %module example %inline %{ %nodefaultctor Foo; // No default constructor for Foo typedef struct Foo { // No default constructor generated. int x; } Foo; typedef struct Bar { int y; Foo f; // Default constructor generated. } Bar; %}
注意:还有 -nodefault 选项和 %nodefault 指令,它们禁用默认或隐式析构函数生成。这可能会导致目标语言内存泄漏,强烈建议你不要使用它们。
/* file : example.h */ typedef struct Vector { double x, y, z; } Vector;可以通过编写如下所示的 SWIG 接口使 Vector 看起来很像一个类:
// file : example.i %module mymodule %{ #include "example.h" %} %include "example.h" // Just grab original C header file %extend Vector { // Attach these functions to struct Vector Vector(double x, double y, double z) { Vector *v; v = (Vector *) malloc(sizeof(Vector)); v->x = x; v->y = y; v->z = z; return v; } ~Vector() { free($self); } double magnitude() { return sqrt($self->x*$self->x+$self->y*$self->y+$self->z*$self->z); } void printVector() { printf("Vector [%g, %g, %g]\n", $self->x, $self->y, $self->z); } };# script.py import example vector = example.Vector(3, 4, 0) print(vector.magnitude()) # 5.0 vector.printVector() # Vector [3, 4, 0]%extend 指令也可以在 Vector 结构体的定义中使用。例如:
/* example.i */ %module example %{ #include "example.h" %} typedef struct Vector { double x, y, z; %extend { Vector(double x, double y, double z) { Vector *v; v = (Vector *) malloc(sizeof(Vector)); v->x = x; v->y = y; v->z = z; return v; } ~Vector() { free($self); } } } Vector;请注意,%extend 可用于访问外部编写的函数,前提是它们遵循此示例中使用的命名约定:
// example.h typedef struct Vector { int x, y, z; } Vector;// example.c #include "example.h" Vector *new_Vector(int x, int y, int z) { Vector *v; v = (Vector *)malloc(sizeof(Vector)); v->x = x; v->y = y; v->z = z; printf("v->x=%d, v->y=%d, v->z=%d\n", v->x, v->y, v->z); return v; } void delete_Vector(Vector *v) { free(v); } int Vector_magnitude(Vector *v) { return v->x*v->x + v->y*v->y + v->z*v->z; }/* example.i */ %module example %{ #include "example.h" %} typedef struct Vector { int x, y, z; %extend { Vector(int, int, int); // This calls new_Vector() ~Vector(); // This calls delete_Vector() int magnitude(); // This will call Vector_magnitude() } } Vector;# script.py import example vector = example.Vector(3, 4, 5) print(vector.magnitude()) # 50
%extend 用的名字和结构体的名字相同,而非结构体的 typedef。例如:
/* example.i */ %module example %{ #include "example.h" %} typedef struct Integer { int value; } Int; %extend Integer { ... } /* Correct name */ %extend Int { ... } /* Incorrect name */ struct Float { float value; }; typedef struct Float FloatValue; %extend Float { ... } /* Correct name */ %extend FloatValue { ... } /* Incorrect name */有一个例外,就是当结构体是匿名的时候,例如:
typedef struct { double value; } Double; %extend Double { ... } /* Okay */%extend 指令的一个鲜为人知的功能是它还可以用于添加合成属性或修改现有数据属性的行为。类似将方法修改成属性, 即Python的 property功能 例如,假设你想要 magnitude 是 Vector 的只读属性而不是方法。为此,你可以编写如下代码:
// example.c #include "example.h" Vector *new_Vector(int x, int y, int z) { Vector *v; v = (Vector *)malloc(sizeof(Vector)); v->x = x; v->y = y; v->z = z; printf("v->x=%d, v->y=%d, v->z=%d\n", v->x, v->y, v->z); return v; } void delete_Vector(Vector *v) { free(v); }/* example.i */ %module example %{ #include "example.h" %} typedef struct Vector { double x, y, z; %extend { Vector(int, int, int); // This calls new_Vector() ~Vector(); // This calls delete_Vector() } } Vector; // Add a new attribute to Vector %extend Vector { const int magnitude; } // Now supply the implementation of the Vector_magnitude_get function %{ const int Vector_magnitude_get(Vector *v) { return (const int) v->x*v->x+v->y*v->y+v->z*v->z; } %}现在,出于所有实际目的,magnitude 将看起来像对象的属性。
类似的也可用于处理你要处理的数据成员。例如,考虑这个接口:// example.h typedef struct Person { char name[50]; } Person;/* example.i */ %module example %{ #include "example.h" %} typedef struct Person { %extend { char name[50]; } } Person; %{ #include <string.h> #include <ctype.h> void make_upper(char *name) { char *c; for (c = name; *c; ++c) *c = (char)toupper((int)*c); } /* Specific implementation of set/get functions forcing capitalization */ char *Person_name_get(Person *p) { make_upper(p->name); return p->name; } void Person_name_set(Person *p, char *val) { strncpy(p->name, val, 50); make_upper(p->name); } %}# script.py import example person = example.Person() person.name = "hello" print(person.name) # HELLO
typedef struct Object { int objtype; union { int ivalue; double dvalue; char *strvalue; void *ptrvalue; } intRep; } Object;当 SWIG 遇到此问题时,它会执行结构体拆分操作,将声明转换为以下等效项:
typedef union { int ivalue; double dvalue; char *strvalue; void *ptrvalue; } Object_intRep; typedef struct Object { int objType; Object_intRep intRep; } Object;然后,SWIG 将创建一个 Object_intRep 结构,以便在接口文件中使用。将为两个结构创建访问器功能。在这种情况下,将创建这样的函数:
Object_intRep *Object_intRep_get(Object *o) { return (Object_intRep *) &o->intRep; } int Object_intRep_ivalue_get(Object_intRep *o) { return o->ivalue; } int Object_intRep_ivalue_set(Object_intRep *o, int value) { return (o->ivalue = value); } double Object_intRep_dvalue_get(Object_intRep *o) { return o->dvalue; }虽然这个过程有点惊险,但它的工作方式与目标脚本语言中的预期相同——尤其是在使用代理类时。
# script.py import example obj = example.Object() obj_inQ = example.Object_intRep() obj_inQ.ivalue = 10