四、SWIG 之 预处理与参数处理
一、预处理
1.1 文件包含(%include)
请使用 %include 指令将另一个文件包含到 SWIG 接口文件中,如下所示:
%include "pointer.i"与 #include 不同,对于每个文件 %include 仅包含一次(并且不会在随后的 %include 声明中重新加载文件)。因此,没有必要在 SWIG 接口文件中使用包含保护。
%module my_module %{ #include "MyHeader.h" %} %include "pointer.i"这些东西%{ ... %}直接传递给输出; 它本身不是由SWIG解释的.因此,#include确保生成的C/C++代码包含该标头。
%include相比之下,是一个SWIG指令.它告诉SWIG在继续之前处理该头文件。这样,SWIG将了解(并生成包装)该头文件中声明的类型和函数。
如果标头非常复杂,它可能会混淆SWIG或导致非常大的输出(因为SWIG尝试为其中的所有内容生成包装器).在这种情况下,最好手动声明需要SWIG处理的标题部分,并省略%include.但是你仍然可能想要#include生成的C++来编译。
个人某种理解就是 #include导入的就是自己写的函数或者类所需要的自己代码写的.h。 %include导入的就是内置的 .i 文件(也有可能是 .h 文件)
%import "foo.i"%import 的目的是从另一个 SWIG 接口文件或头文件中收集某些信息,而无需实际生成任何包装代码。此类信息通常包括类型声明(例如 typedef)以及可用作接口中类声明的 C++ 基类。当使用 SWIG 生成扩展作为相关模块的集合时,使用 %import 也很重要。
SWIG 完全支持使用 #if、#ifdef、#ifndef、#else 和 #endif 来有条件地包含接口的各个部分。
/* example.i */ %module example %inline %{ void foo() { #ifdef x printf("x = %d\n", x); #else printf("x = %d\n", 10); #endif } %}# script.py import example example.foo() # x = 10
// SWIG 在解析接口时预定义了以下符号: SWIG Always defined when SWIG is processing a file SWIGIMPORTED Defined when SWIG is importing a file with %import SWIG_VERSION Hexadecimal (binary-coded decimal) number containing SWIG version, such as 0x010311 (corresponding to SWIG-1.3.11). SWIGALLEGROCL Defined when using Allegro CL SWIGCFFI Defined when using CFFI SWIGCHICKEN Defined when using CHICKEN SWIGCLISP Defined when using CLISP SWIGCSHARP Defined when using C# SWIGGUILE Defined when using Guile SWIGJAVA Defined when using Java SWIGJAVASCRIPT Defined when using Javascript SWIG_JAVASCRIPT_JSC Defined when using Javascript for JavascriptCore SWIG_JAVASCRIPT_V8 Defined when using Javascript for v8 or node.js SWIGLUA Defined when using Lua SWIGMODULA3 Defined when using Modula-3 SWIGMZSCHEME Defined when using Mzscheme SWIGOCAML Defined when using Ocaml SWIGOCTAVE Defined when using Octave SWIGPERL Defined when using Perl SWIGPHP Defined when using PHP5 or PHP7 SWIGPHP5 Defined when using PHP5 SWIGPHP7 Defined when using PHP7 SWIGPIKE Defined when using Pike SWIGPYTHON Defined when using Python SWIGR Defined when using R SWIGRUBY Defined when using Ruby SWIGSCILAB Defined when using Scilab SWIGSEXP Defined when using S-expressions SWIGTCL Defined when using Tcl SWIGXML Defined when using XML此外,SWIG 定义了以下标准 C/C++ 宏集合:
__LINE__ Current line number __FILE__ Current file name __STDC__ Defined to indicate ANSI C __cplusplus Defined when -c++ option used
接口文件可以根据需要查看这些符号,以更改生成接口的方式,或将 SWIG 指令与 C 代码混合。这些符号也在 SWIG 生成的 C 代码中定义(符号 SWIG 仅在 SWIG 编译器中定义)。
传统的预处理器宏可以在 SWIG 接口中使用。注意,#define 语句也用于尝试和检测常量。因此,如果文件中包含类似的内容,
#ifndef _FOO_H 1 #define _FOO_H 1 #endif可能会在脚本接口中看到一些额外的常量,例如 _FOO_H 。
可以用标准方式定义更复杂的宏。例如:
#define EXTERN extern #ifdef __STDC__ #define _ANSI(args) (args) #else #define _ANSI(args) () #endif// 以下运算符可以出现在宏定义中: #x // 将宏参数 x 转换为双引号("x")包围的字符串。 x ## y // 将 x 和 y 串联在一起形成 xy。 x // 如果 x 是用双引号引起来的字符串,则不执行任何操作。否则,将其转换为类似于 #x 的字符串。这是非标准的 SWIG 扩展。
SWIG 通过 %define 和 %enddef 指令提供增强的宏功能。例如:
/* example.i */ %module example %define ARRAYHELPER(type, name) %inline %{ type *new_ ## name (int nitems) { return (type *) malloc(sizeof(type)*nitems); } void delete_ ## name(type *t) { free(t); } type name ## _get(type *t, int index) { return t[index]; } void name ## _set(type *t, int index, type val) { t[index] = val; } %} %enddef ARRAYHELPER(int, IntArray) ARRAYHELPER(double, DoubleArray)# script.py import example array = example.new_IntArray(10) example.IntArray_set(array, 0, 10) example.IntArray_set(array, 1, 20) example.IntArray_set(array, 2, 30) print(example.IntArray_get(array, 0)) # 10 print(example.IntArray_get(array, 1)) # 20 print(example.IntArray_get(array, 2)) # 30%define 的主要目的是定义大型的代码宏。与普通的 C 预处理器宏不同,它不必以连续字符(\)结束每一行——宏定义扩展到第一次出现 %enddef 的地方。此外,扩展此类宏时,将通过 C 预处理器对其进行重新解析。因此,除了嵌套的 %define 语句以外,SWIG 宏可以包含的所有其他预处理器指令。
SWIG 宏功能是一种非常快速简便地生成大量代码的方法。实际上,SWIG 的许多高级功能和库都是使用此机制构建的(例如对 C++ 模板支持)。
预处理器以不同的方式处理 {...}、"..." 和 %{...%} 分隔符。
SWIG 预处理器不处理代码块 %{...%} 中包含的任何文本。因此,如果你编写这样的代码,
%{ #ifdef NEED_BLAH int blah() { ... } #endif %}%{...%} 块的内容被复制并输出(包括所有预处理指令)而不做修改。某种意义上就是将内容进行声明的意思,有点像是 .h 文件
SWIG 始终对出现在 {...} 中的文本运行预处理器。但是,有时希望使预处理器指令传递到输出文件。例如:
// example.h typedef struct { int x; } Foo; #ifndef DEBUG #define DEBUG #endif%extend Foo { void bar() { #ifdef DEBUG printf("I'm in bar\n"); #endif } }# script.py import example foo = example.Foo() foo.bar() # I'm in bar默认情况下,SWIG 将解释 #ifdef DEBUG 语句。但是,如果真的希望该代码真正进入包装文件,请在预处理器指令前加上 % 前缀,如下所示:
/* example.i */ %module example %{ #include "example.h" %} %include "example.h" %extend Foo { void bar() { %#ifdef DEBUG printf("I'm in bar\n"); %#endif } }SWIG 将剥离多余的 % 并将预处理器指令保留在代码中。
# script.py import example foo = example.Foo() foo.bar() # I'm in bar
#define SWIG_macro(CAST) (CAST)$input %typemap(in) Int {$1= SWIG_macro(int);}也许会生成
{ arg1=(int)jarg1; }然而
#define SWIG_macro(CAST) (CAST)$input %typemap(in, noblock=1) Int {$1= SWIG_macro(int);}也许会生成
arg1=(int)jarg1;
以及
#define SWIG_macro(CAST) (CAST)$input %typemap(in) Int %{$1=SWIG_macro(int);%}也许会生成
arg1=SWIG_macro(int);
SWIG 支持常用的 #warning 和 #error 预处理器指令。#warning 指令将使 SWIG 发出警告,然后继续处理。#error 指令将导致 SWIG 退出并出现致命错误。用法示例:
#error "This is a fatal error message" #warning "This is a warning message"如果使用了 -cpperraswarn 命令行选项,则可以使 #error 行为像 #warning 一样工作。另外,也可以使用 #pragma 指令达到相同的效果,例如:
/* Modified behaviour: #error does not cause SWIG to exit with error */ #pragma SWIG cpperraswarn=1 /* Normal behaviour: #error does cause SWIG to exit with error */ #pragma SWIG cpperraswarn=0
二、参数处理
2.1 typemaps.i 库
有如下 C 函数:
void add(double a, double b, double *result) { *result = a + b; }明显该函数将值存储在 double *result 参数中。但是,由于 SWIG 不检查函数主体,因此没有办法知道函数的底层行为。
一种解决方法是使用 typemaps.i 库文件,并编写如下接口代码:
// Simple example using typemaps %module example %include "typemaps.i" %apply double *OUTPUT { double *result }; // %apply double *OUTPUT { double * };好像也可以 %inline %{ extern void add(double a, double b, double *result); %}%apply 指令告诉 SWIG 将要对某类型应用特殊的类型处理规则。double *OUTPUT 规范是一个规则的名称,OUTPUT可以理解为内置名称,不能随便使用。该规则定义了如何从类型为 double * 的参数返回输出值。该规则将应用于大括号中列出的所有数据类型,在这个例子中为 double *result。
# script.py import example foo = example.add(3, 4) print(foo) # 7在这个例子中,可以看到通常在第三个参数中返回的输出值如何神奇地转换为函数返回值。显然,这使函数更易于使用,因为不再需要制造特殊的 double * 对象,并将其以某种方式传递给函数。
一旦将类型映射应用于类型后,它对于以后所有出现的类型和名称都保持有效。例如,可以编写以下内容:
// example.c void add(double a, double b, double *result) { *result = a + b; } void sub(double a, double b, double *result) { *result = a - b; } void mul(double a, double b, double *result) { *result = a * b; }/* example.i */ %module example %include "typemaps.i" %apply double *OUTPUT { double * }; %inline %{ extern void add(double a, double b, double *result); extern void sub(double a, double b, double *result); extern void mul(double a, double b, double *result); %}# script.py import example print(example.add(4, 2)) # 6.0 print(example.sub(4, 2)) # 2.0 print(example.mul(4, 2)) # 8.0类型映射转换甚至可以扩展为多个返回值
// example.c void getwinsize(int winid, int *width, int *height) { *width = winid / 4; *height = winid / 6; }/* example.i */ %module example %include "typemaps.i" %apply int *OUTPUT { int *width, int *height }; %inline %{ extern void getwinsize(int winid, int *width, int *height); %}# script.py import example width, height = example.getwinsize(12) print(width, height) # 3 2尽管已经使用 %apply 指令将类型映射规则与数据类型相关联,但是你也可以直接在参数中使用规则名称。例如,可以这样编写接口文件:
// example.c void add(double a, double b, double *OUTPUT) { *OUTPUT = a + b; }/* example.i */ %module example %include "typemaps.i" %{ extern void add(double a, double b, double *OUTPUT); %} extern void add(double a, double b, double *OUTPUT);# script.py import example print(example.add(3, 4)) # 7.0类型映射将一直有效,直到将其明确删除或重新定义为其他类型为止。要清除类型映射,应使用 %clear 指令。例如:
// example.c void add(double a, double b, double *OUTPUT) { *OUTPUT = a + b; } void sub(double a, double b, double *OUTPUT) { *OUTPUT = a - b; }/* example.i */ %module example %include "typemaps.i" %{ extern void add(double a, double b, double *OUTPUT); extern void sub(double a, double b, double *OUTPUT); %} extern void add(double a, double b, double *OUTPUT); %clear double *OUTPUT; // Remove all typemaps for double *result extern void sub(double a, double b, double *OUTPUT);
这样第三个参数就变成了正常的需要传递的参数了,不是返回的结果了。
以下类型映射告诉 SWIG,指针实际上仅保存一个输入值:
int *INPUT short *INPUT long *INPUT unsigned int *INPUT unsigned short *INPUT unsigned long *INPUT double *INPUT float *INPUT使用时,它允许传递值而不是指针。例如,考虑以下函数:
// example.c double add(double *a, double *b) { return *a + *b; }/* example.i */ %module example %include "typemaps.i" %{ extern double add(double *, double *); // 参数可以只声明类型,不带参数 %} extern double add(double *INPUT, double *INPUT);# script.py import example print(example.add(3, 4)) # 7.0
以下类型映射规则告诉 SWIG,指针是函数的输出值。使用时,在调用函数时不需要提供参数。而是返回一个或多个输出值。
int *OUTPUT short *OUTPUT long *OUTPUT unsigned int *OUTPUT unsigned short *OUTPUT unsigned long *OUTPUT double *OUTPUT float *OUTPUT可以如先前示例中所示使用这些方法。例如,如果你具有以下 C 函数:
// example.c void add(double a, double b, double *c) { *c = a + b; }/* example.i */ %module example %include "typemaps.i" %{ extern void add(double, double, double *); %} extern void add(double a, double b, double *OUTPUT);# script.py import example print(example.add(3, 4)) # 7.0如果函数还返回值,则将其与参数一起返回。例如,如果你有:
// example.c double add(double a, double b, double *c) { *c = a + b; return a; }/* example.i */ %module example %include "typemaps.i" %{ extern double add(double, double, double *); %} extern double add(double a, double b, double *OUTPUT);# script.py import example func, intP = example.add(3, 4) print(func, intP) # 3.0 7.0 第一个返回值是函数的返回值,第二个返回值是 OUTPUT 的值
int *INOUT short *INOUT long *INOUT unsigned int *INOUT unsigned short *INOUT unsigned long *INOUT double *INOUT float *INOUT有这样一个 C 函数:
// example.c void negate(double *x) { *x = -(*x); }要使 x 既作为函数的输入值又作为输出值,请在接口文件中声明如下函数:
/* example.i */ %module example %include "typemaps.i" %{ extern void negate(double *); %} extern void negate(double *INOUT);# script.py import example print(example.negate(3)) # -3.0INOUT 规则的一个细微之处是,许多脚本语言对原始对象实施了可变性约束(这意味着简单的对象,如整数和字符串,不应更改)。因此,不能像在此示例中基础 C 函数那样就地修改对象的值。因此,INOUT 规则将修改后的值作为新对象返回,而不是直接覆盖原始输入对象的值。
// example.c void negate(double *x) { *x = -(*x); } void add(double a, double b, double *OUTPUT) { *OUTPUT = a + b; } double sub(double *a, double *b) { return *a - *b; }/* example.i */ %module example %include "typemaps.i" %{ extern void negate(double *); extern void add(double, double, double *); extern double sub(double *, double *); %} extern void negate(double *INOUT); extern void add(double a, double b, double *OUTPUT); extern double sub(double *INPUT, double *INPUT); // 可以上面这样使用,也可以使用 %apply# script.py import example print(example.negate(3)) # -3.0 print(example.add(3, 4)) # 7.0 print(example.sub(3, 4)) # -1.0
除了更改对各种输入值的处理之外,还可以使用类型映射来应用约束。例如,也许你想确保一个值是正数,或者一个指针是非 NULL 的。这可以通过包含 constraints.i 库文件来完成。个人认为,几乎用不到这个功能
以下接口文件是对 constraints.i 库最好的说明, 该文件的行为与期望的完全一样。如果任何参数违反约束条件,将引发脚本语言异常。最终,可能会捕获错误的值,防止神秘的程序崩溃,等等。
%include "constraints.i" double exp(double x); double log(double POSITIVE); // Allow only positive values double sqrt(double NONNEGATIVE); // Non-negative values only double inv(double NONZERO); // Non-zero values void free(void *NONNULL); // Non-NULL pointers onlyPOSITIVE Any number > 0 (not zero) NEGATIVE Any number < 0 (not zero) NONNEGATIVE Any number >= 0 NONPOSITIVE Any number <= 0 NONZERO Nonzero number NONNULL Non-NULL pointer (pointers only).
约束库仅支持原始 C 数据类型,但使用 %apply 可以很容易地将其应用于新的数据类型。例如 :
// Apply a constraint to a Real variable %apply Number POSITIVE { Real in }; // Apply a constraint to a pointer type %apply Pointer NONNULL { Vector * };特殊类型的 Number 和 Pointer 可以分别应用于任何数字和指针变量类型。为了以后删除约束,可以使用 %clear 指令:
%clear Real in; %clear Vector *;