三、SWIG 之 指针与复杂对象
// SWIG 完全支持指向原始 C 数据类型的指针。 int * double *** char **SWIG 不试图将指向的数据转换为脚本表示,而是简单地将指针本身编码为包含指针实际值和类型标记的表示。
NULL 指针由字符串 NULL 或用类型信息编码的值 0 表示。
SWIG 将所有指针视为不透明对象。因此,指针可以由函数返回并根据需要传递给其他 C 函数。出于所有实际目的,脚本语言接口的工作方式与在 C 程序中使用指针的方式完全相同。唯一的区别是没有解引用指针的机制,因为这需要目标语言来理解底层对象的内存布局。
永远不要直接操作指针值的脚本语言表示。尽管所示的值看起来像十六进制地址,但所使用的数字可能与实际的机器地址不同(例如,在小端机器上,数字可能以相反的顺序出现)。此外,SWIG 通常不会将指针映射到高级对象,例如关联数组或列表(例如,将 int * 转换为整数列表)。SWIG 不这样做有几个原因:
1. C 声明中没有足够的信息来将指针正确映射到更高级别的结构。例如,int * 可能确实是一个整数数组,但如果它包含一千万个元素,将它转换为一个列表对象可能是一个坏主意。
2. SWIG 不知道与指针相关的基础语义。例如,int * 可能根本不是一个数组——也许它是一个输出值!
3. 通过以一致的方式处理所有指针,SWIG 的实现大大简化并且不容易出错。
通过允许从脚本语言操作指针,扩展模块有效地绕过了 C/C++ 编译器中的编译时类型检查。为了防止错误,类型签名被编码到所有指针值中,并用于执行运行时类型检查。此类型检查过程是 SWIG 的组成部分,不使用类型映射就无法禁用或修改(在后面的章节中有介绍)。
像 C 一样,void * 匹配任何类型的指针。此外,NULL 指针可以传递给任何期望接受指针的函数。虽然这有可能导致崩溃,但 NULL 指针有时也用作标记值或表示缺失/空值。因此,SWIG 将 NULL 指针对应到应用程序。
对于其他一切(结构体、类、数组等),SWIG 应用了一个非常简单的规则:一切皆是指针
换句话说,SWIG 通过引用操纵其他所有内容。这个模型很有意义,因为大多数 C/C++ 程序都大量使用指针,SWIG 可以使用已经存在的指针类型检查机制来处理指向基本数据类型的指针。虽然这可能听起来很复杂,但它确实非常简单。假设你有一个这样的接口文件:
// example.c .../* example.i */ %module example %{ FILE *fopen(char *, char *); int fclose(FILE *); unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *); unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *); void *malloc(int nbytes); void free(void *); %} FILE *fopen(char *, char *); int fclose(FILE *); unsigned fread(void *ptr, unsigned size, unsigned nobj, FILE *); unsigned fwrite(void *ptr, unsigned size, unsigned nobj, FILE *); void *malloc(int nbytes); void free(void *);# script.py import example # Copy a file def file_copy(source, target): f1 = example.fopen(source, "r") f2 = example.fopen(target, "w") buffer = example.malloc(8192) byte = example.fread(buffer, 8192, 1, f1) while byte > 0: example.fwrite(buffer, 8192, 1, f2) byte = example.fread(buffer, 8192, 1, f1) example.free(buffer) if __name__ == '__main__': file_copy("1.txt", "2.txt")在这种情况下,f1、f2 和 buffer 都是包含 C 指针的不透明对象。它们包含什么值并不重要——我们的程序在没有这些知识的情况下工作得很好。
当 SWIG 遇到未声明的数据类型时,它会自动假定它是结构体或类。例如,假设 SWIG 输入文件中出现以下函数:
void matrix_multiply(Matrix *a, Matrix *b, Matrix *c);
SWIG 不知道 Matrix 是什么。但是,它显然是指向某事物的指针,因此 SWIG 使用其通用指针处理代码生成包装器。
与 C 或 C++ 不同,SWIG 实际上并不关心先前是否在接口文件中定义了 Matrix。这允许 SWIG 仅从部分或有限信息生成接口。在某些情况下,只要你可以在脚本语言接口中传递一个不透明的引用,你可能不关心 Matrix 是什么。
需要注意的一个重要细节是,当存在未指定的类型名称时,SWIG 将很乐意为接口生成包装器。但是,所有未指定的类型都在内部处理为指向结构体或类的指针!例如:/* example.i */ %module example %{ void foo(size_t num); %} void foo(size_t num);# script.py # 如果 size_t 未声明,SWIG 会生成期望接收 size_t * 类型的包装器。 foo(40); TypeError: expected a _p_size_t.
与 C 一样,typedef 可用于在 SWIG 中定义新的类型名称。例如:
typedef unsigned int size_t;
出现在 SWIG 接口中的 typedef 定义不会传播到生成的包装器代码。因此,它们需要在包含的头文件中定义,或者放在声明部分中,如下所示:下面两种的效果是一样的,都能生成外部接口以供使用
/* example.i */ %module example %{ /* Include in the generated wrapper file */ typedef unsigned int size_t; %} /* Tell SWIG about it */ typedef unsigned int size_t; // 或者下面的也行 /* example.i */ %module example %inline %{ typedef unsigned int size_t; %}