5.5 结构体与联合体

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

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

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);
}

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

v = new_Vector()
Vector_x_set(v,2)
Vector_y_set(v,10)
Vector_z_set(v,-5)
...
delete_Vector(v)

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

5.5.1 Typedef与structures

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

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是一样的,它会生成合适的类型检查代码。

5.5.2 字符串与结构体

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

%module mymodule
...
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的typemap memberin可用于改变其行为。查看typemap相关章节了解更多信息。

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

5.5.2 数组成员

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

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

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

5.5.3 结构体作为数据成员

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

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

5.5.5 C构造器与析构器

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

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

swig -nodefaultctor example.i

或:

%module foo
...
%nodefaultctor; // Don't create default constructors
... declarations ...
%clearnodefaultctor; // Re-enable default constructors

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

%nodefaultctor Foo; // No default constructor for Foo
...
struct Foo { // No default constructor generated.
};
struct Bar { // Default constructor generated.
};

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

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

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

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

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

/* file : vector.h */
...
typedef struct Vector {
    double x,y,z;
} Vector;

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

%module mymodule

%{
#include "vector.h"
%}

%include "vector.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 print() {
      printf("Vector [%g, %g, %g]\n", $self->x,$self->y,$self->z);
  }
};

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

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

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

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

// file : vector.i
%module mymodule
%{
#include "vector.h"
%}
typedef struct Vector {
  double x,y,z;
  %extend {
  Vector(double x, double y, double z) { ... }
  ~Vector() { ... }
  ...
  }
} Vector;

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

/* File : vector.c */
/* Vector methods */
#include "vector.h"

Vector *new_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;
}
void delete_Vector(Vector *v) {
    free(v);
}
double Vector_magnitude(Vector *v) {
    return sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
}
// File : vector.i
// Interface file
%module mymodule
%{
#include "vector.h"
%}
typedef struct Vector {
  double x,y,z;
  %extend {
  Vector(int,int,int); // This calls new_Vector()
  ~Vector(); // This calls delete_Vector()
  double magnitude(); // This will call Vector_magnitude()
  ...
  }
} Vector;

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

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

// Add a new attribute to Vector
%extend Vector {
    const double magnitude;
}
// Now supply the implementation of the Vector_magnitude_get function
%{
const double Vector_magnitude_get(Vector *v) {
    return (const double) sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
}
%}

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

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

typedef struct Person {
  char name[50];
  ...
} Person;

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

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);
}
%}

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

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

5.5.7 内嵌的结构体

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

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;
}
... etc ...

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

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

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

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

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

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

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

double Vector_x_get(Vector *v) {
    return v->x;
}

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

static int
_wrap_Vector_x_get(ClientData clientData, Tcl_Interp *interp,int objc, Tcl_Obj *CONST objv[]) {
  struct Vector *arg1 ;
  double result ;

  if (SWIG_GetArgs(interp, objc, objv,"p:Vector_x_get self ",&arg0,SWIGTYPE_p_Vector) 
                              == TCL_ERROR)
      return TCL_ERROR;

  result = (double ) (arg1->x);
  Tcl_SetObjResult(interp,Tcl_NewDoubleObj((double) result));

  return TCL_OK;
}

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

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

results matching ""

    No results matching ""