五、SWIG 之 结构体与共用体

一、SWIG对结构体的基本设计

如果 SWIG 遇到结构体和共用体的定义,它将创建一组访问器函数。虽然 SWIG 不需要结构体定义来构建接口,但提供定义可以使其访问结构体成员。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 与结构体

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 相同,并且它生成适当的类型检查代码。

三、字符串与结构体

涉及字符串的结构体需要一些小心。SWIG 假定 char * 类型的所有成员都是使用 malloc() 动态分配的,并且它们是以 NULL 结尾的 ASCII 字符串。修改此类成员后,将释放先前的内容,并分配新内容。例如 :

/* 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 来执行内存分配。

四、数组成员

数组可能显示为结构体的成员,但它们将是只读的。SWIG 将编写一个访问器函数,该函数返回指向数组第一个元素的指针,但不会编写一个函数来更改数组本身的内容。检测到这种情况时,SWIG 可能会生成一条警告消息,如下所示:

/* 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;
}

六、C 构造函数和析构函数

在包装结构体时,通常有一种用于创建和销毁对象的机制。如果你什么都不做,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 指令,它们禁用默认或隐式析构函数生成。这可能会导致目标语言内存泄漏,强烈建议你不要使用它们。

七、向 C 结构体添加成员函数

大多数语言都提供了一种创建类和支持面向对象编程的机制。从 C 的角度来看,面向对象编程实际上归结为将函数附加到结构体的过程。这些函数通常在结构体(或对象)的实例上运行。虽然 C++ 有这样一种自然的映射方式,但是没有直接的机制可以将它与 C 代码一起使用。但是,SWIG 提供了一个特殊的 %extend 指令,可以将方法附加到 C 结构体以构建面向对象的接口。假设你有一个带有以下声明的 C 头文件:

/* 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

八、嵌套结构体

有时候,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;
}

 虽然这个过程有点惊险,但它的工作方式与目标脚本语言中的预期相同——尤其是在使用代理类时。

# script.py

import example

obj = example.Object()
obj_inQ = example.Object_intRep()
obj_inQ.ivalue = 10

 

posted on 2022-11-22 17:19  软饭攻城狮  阅读(166)  评论(0编辑  收藏  举报

导航