11.1 介绍
11.1.1 类型转换(Type Conversion)
在编程语言之间做数据类型的装换(conversion)或列集(marshalling)是包装代码生成器(wrapper code generator)的一项中非常重要的工作。对每一种c/C++的类型声明(declaration),SWIG必须以 某种方式生成包装代码用以在语言间来回传递数据的值(value)。由于每种编程语言表达数据的方式不 同,因此不能简单将代码使用C连接器连接在一起。SWIG需要知道每种语言是如何表达数据的,并且需要 知道怎样去操作它们。
为说明问题,假设你有下面一段简单的C代码:
int factorial(int n);
为了能从Python中访问该函数,需要使用一对Python API函数转换整形数据。例如:
long PyInt_AsLong(PyObject *obj); /* Python --> C */
PyObject *PyInt_FromLong(long x); /* C --> Python */
第一个函数用来将输入参数Python整形对象转换为C语言的long类型。第二个函数用来将C语言的long型 数值转换回Python的整形对象。
在包装函数内部,你可能看到类似下面的下面的函数:
PyObject *wrap_factorial(PyObject *self, PyObject *args) {
int arg1;
int result;
PyObject *obj1;
PyObject *resultobj;
if (!PyArg_ParseTuple("O:factorial", &obj1)) return NULL;
arg1 = PyInt_AsLong(obj1);
result = factorial(arg1);
resultobj = PyInt_FromLong(result);
return resultobj;
}
SWIG支持的每一种目标语言都有类似的转换函数。比如,在Perl中,使用如下的函数:
IV SvIV(SV *sv); /* Perl --> C */
void sv_setiv(SV *sv, IV val); /* C --> Perl */
在TCL中:
int Tcl_GetLongFromObj(Tcl_Interp *interp, Tcl_Obj *obj, long *value);
Tcl_Obj *Tcl_NewIntObj(long value);
在这里,具体细节不是那么重要。重要的是,所有的底层类型转换都使用类似的功能函数,如果你对语言 扩展感兴趣可以阅读各种语言相关的文档,这些实验就留给你们自己去练习吧。
11.1.2 Typemaps
因为类型转换时代码包装生成器的中心工作,SWIG允许用户完全定义(或重定义)。为此,你可以使用特
殊的%typemap指令。例如:
/* Convert from Python --> C */
%typemap(in) int {
$1 = PyInt_AsLong($input);
}
/* Convert from C --> Python */
%typemap(out) int {
$result = PyInt_FromLong($1);
}
第一次看到这样的代码,你肯定会迷迷糊糊地。但这真的是没什么大不了的。第一个typemap(“in” typemap)用来从目标语言转换数值(value)到C语言。第二个typemap(“out” typemap)用于从C语言转换数据到目标语言。每个typemap的内容都是一小段代码片段,直接插入SWIG生成的包装函数代码中。这些代码一般是C或C++代码,通过包装代码生成器处理后插入包装函数中。需要注意的是,某些目标语言的typemap也允许目标语言代码的插入。在这些代码中,带\$前缀的特殊变量会自动展开。这些变量只是C/C++语言变量的占位符号,经处理后会插入最终的包装代码中。\$input表示需要转换到C/C++的输入对象,$result表示包装函数要返回的对象。\$1表示C/C++变量,它的类型就是typemap申明中(这个例子中的int
)指定的。
给一个简单的例子更好理解。如果你想包装如下的函数:
int gcd(int x, int y);
包装函数可能如下:
PyObject *wrap_gcd(PyObject *self, PyObject *args) {
int arg1;
int arg2;
int result;
PyObject *obj1;
PyObject *obj2;
PyObject *resultobj;
if (!PyArg_ParseTuple("OO:gcd", &obj1, &obj2)) return NULL;
/* "in" typemap, argument 1 */
{
arg1 = PyInt_AsLong(obj1);
}
/* "in" typemap, argument 2 */
{
arg2 = PyInt_AsLong(obj2);
}
result = gcd(arg1, arg2);
/* "out" typemap, return value */
{
resultobj = PyInt_FromLong(result);
}
return resultobj;
}
在这段代码中,你能看到typemap是如何插入到生成的函数中的。你也可以看到特殊变量$是如何匹配特定变量名,并在包装函数中展开的。这就是typemap的全部思想,它们可以让你插入任意代码到生成的包装函数的不同地方。因为人意代码都可以插入,它可能完全改变值被改变的方式。
11.1.3 模式匹配(Pattern Matching)
正如名字所暗含的意思,typemap的目的就是在目标语言中映射(“map“)C语言数据类型。一旦某个C语言数据类型的typemap被定义,输入文件中所有出现的该类型都会应用其特征。例如:
/* Convert from Perl --> C */
%typemap(in) int {
$1 = SvIV($input);
}
...
int factorial(int n);
int gcd(int x, int y);
int count(char *s, char *t, int max);
匹配typemap到其相应的C语言数据类型不是简单的文本匹配。事实上,typemap全面内置(builtin)于底层的类型系统中。因此,typemap并不受typedef、namespace或其他可能会隐藏底层类型的声明的影响。例如,可能你有如下代码:
/* Convert from Ruby--> C */
%typemap(in) int {
$1 = NUM2INT($input);
}.
..
typedef int Integer;
namespace foo {
typedef Integer Number;
};
int foo(int x);
int bar(Integer y);
int spam(foo::Number a, foo::Number b);
这种情况下,typemap依然可以应用到合适的参数上,即使typemap的类型名不总能匹配int
。实际上,这种跟踪类型的能力是SWIG的重要部分,所有的目标语言模块都为基础类型定义了一组typemap。同时,没有必要为typedef
定义新的typemap。
除了跟踪类型名字,typemap还可以特别指定去匹配特定的参数名。例如,你写了如下代码:
%typemap(in) double nonnegative {
$1 = PyFloat_AsDouble($input);
if ($1 < 0) {
PyErr_SetString(PyExc_ValueError, "argument must be nonnegative.");
SWIG_fail;
}
}
...
double sin(double x);
double cos(double x);
double sqrt(double nonnegative);
typedef double Real;
double log(Real nonnegative);
在对输入参数进行转换的情况下,typemap可以被定义成能处理连续参数的样式。例如:
%typemap(in) (char *str, int len) {
$1 = PyString_AsString($input); /* char *str */
$2 = PyString_Size($input); /* int len */
}.
..
int count(char *str, int len, char c);
这种情况下,目标语言的一个输入对象被扩展成一对C语言参数。这个例子还展示了不常用的变量命名方案,$1、$2,诸如此类等。
11.1.4 重用typemap
Typemap一般用于指定的类型和参数名模式。但是,也可以被拷贝和复制。这样做的一种方式是想下面这样赋值:
%typemap(in) Integer = int;
%typemap(in) (char *buffer, int size) = (char *str, int len);
更通用的拷贝形式是使用如下的%apply
指令:
%typemap(in) int {
/* Convert an integer argument */
...
}%
typemap(out) int {
/* Return an integer value */
...
}
/* Apply all of the integer typemaps to size_t */
%apply int { size_t };
%apply
指令仅仅是把针对某种类型的所有typemap应用到另外一种类型上。
需要注意是是,没有必要为某种类型的typedef拷贝新的typemap。例如,如果你有如下代码:
typedef int size_t;
SWIG就已经知道应用int
的typemap了,不需要再做其他工作。
11.1.5 使用typemap能干什么
使用typemap的主要目的就是为C/C++数据类型层面定义包装器的行为。当前typemap可以定位6种通用类别的问题:
参数处理(Argument Handing)
int foo(int x, double y, char *s);
- 输入参数转换(“in” typemap)
- 重载(overloading)函数输入参数类型转换检查(“typecheck” typemap)
- 输出产出处理(“argout” typemap)
- 输入参数值的检查(“check” typemap)
- 输入参数值的初始化(“arginit” typemap)
- 默认参数(“default” typemap)
- 输入参数资源管理(“freearg” typemap)
返回值处理(Return Value Handling)
int foo(int x, double y, char *s);
- 函数返回值转换(”out” typemap)
- 返回值资源管理(“ret” typemap)
- 新分配对象的资源管理(“newfree” typemap)
异常处理(Exception Handling)
int foo(int x, double y, char s) throw(*MemoryError, IndexError);
- 处理C++异常说明(“throw” typemap)
全局变量(Global Variables)
int foo;
- 全局变量的赋值(“varin” typemap)
- 返回全局变量(“varout” typemap)
成员变量(Member Variables)
struct Foo { int x[20]; };
- 对类或结构体的成员进行赋值(“memberin” typemap)
创建常量(Constant Creation)
#define FOO 3
%constant int BAR = 42; enum { ALE, LAGER, STOUT };
- 常量的创建(“consttab”或者”constcode” typemap)
每个typemap我们都会做简短地描述。某些语言的模块也会定义额外的typemap。例如,Java模块就定义了一大堆typemap,用于控制Java绑定(binding)的各个方面。请参考各语言的特定文档了解细节。
11.1.6 Typemap不可以干什么?
Typemap不能用于给C/C++的声明定义属性(properties)。例如,比方说你有下面的声明:
Foo *make_Foo(int n);
你想告诉SWIGmake_Foo(int n)
要返回一个新的分配对象(可能是为了提供更好的内存分配策略)。显然make_Foo(int n)
不是类型Foo *
关联的属性。因此,如果想达到这样的目的,需要使用SWIG提供的另外自定义机制(%feature)。请查看自定义属性章节连接详情。
Typemap也不能用于重新组织或装换参数的顺序。例如,你有如下的函数:
void foo(int, char *);
你不能使用typemap来交换参数,达到如下的目的:
foo("hello",3) # Reversed arguments
如果你想更改函数的调用规则,可以像下面一样写一个帮助函数(helper function):
%rename(foo) wrap_foo;
%inline %{
void wrap_foo(char *s, int x) {
foo(x,s);
}
%}
11.1.7 面向切面编程的相似性
SWIG与面向切面编程(Aspect Oriented Programming)是相似的。Typemap中关于AOP术语可以表述如下:
横切关注点(cross-cutting concerns): 横切关注点就是typemap实现的功能模块,主要用于在目标语言和C/C++语言互相间列集类型。
增强(Advice,也叫通知): typemap的代码体,只要列集需要就会执行。
切点(Pointcut):切点就是typemap生成的包装代码放置的位置。
切面(Aspect):切面就是切点和增强的组合,因此每个typemap都是切面。
SWIG的%feature也可以看做是切面。像%exception这样的特征也具备横切关注点的特征,因为它也可以包装用于添加日志或异常处理的功能。
11.1.8 本章剩下内容要讲什么
本章剩下的内容会给像了解如何编写typemap的人们提供详细信息。这些信息对那些想给新的目标语言编写模块的人特别重要。高级用户可以使用这些信息编写应用特定的类型转换规则。
因为typemap与底层的C++类型系统严格绑定,接下来的章节假设你熟悉一下C++基础概念:值、指针、引用、数组、类型修饰符(如:const)、结构体、命名空间、模板以及内存分配。如果你不了解的话建议去看看Kernighan and Ritchie的《The C Programming Language》和Stroustrup的《The C++ Programming Language》。