13 Typemaps SWIG
13.1 Introduction
你阅读这一章的原因可能有两个;您要么想要定制SWIG的行为,要么无意中听到有人在嘀咕一些关于“typemaps”的难以理解的废话,然后您问自己“typemaps,那些是什么?”也就是说,让我们从一个简短的免责声明开始,即“typemaps”是一种高级定制特性,可以直接访问SWIG的低级代码生成器。不仅如此,它们还是SWIG c++类型系统的重要组成部分(这本身就不是一个简单的主题)。类型映射通常不是使用SWIG的必要部分。因此,如果您在阅读本章时对SWIG在默认情况下已经做了什么只有一个模糊的概念,那么您可能需要重新阅读前面的章节。
13.1.1 Type conversion
包装器代码生成中最重要的问题之一是编程语言之间数据类型的转换或编组。特别是,对于每个C/ c++声明,SWIG必须以某种方式生成允许在语言之间来回传递值的包装器代码。由于每种编程语言都以不同的方式表示数据,所以这不是简单地用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转换回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中,使用了以下函数:
精确的细节并不是那么重要。重要的是,所有底层类型转换都是由实用函数集合和像这样的一小段C代码处理的——您只需要阅读您喜欢的语言的扩展文档就可以知道它是如何工作的(留给读者的练习)。
13.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)用于将一个值从目标语言转换为c语言。第二个typemap(“out”typemap)用于在另一个方向进行转换。每个typemap的内容是一小段代码,直接插入SWIG生成的包装器函数中。代码通常是C或c++代码,这些代码将生成为C/ c++包装器函数。注意,并非总是如此,因为一些目标语言模块允许在类型映射中生成目标语言特定文件的目标语言代码。在这段代码中,扩展了许多以$为前缀的特殊变量。这些实际上只是在创建包装器函数过程中生成的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代码是如何插入到函数中的。您还可以看到如何将特殊的$变量展开以匹配包装器函数中的某些变量名。这实际上就是typemaps背后的全部思想——它们只是让您将任意代码插入生成的包装器函数的不同部分。因为可以插入任意代码,所以可以完全改变转换值的方式。
13.1.3 Pattern matching
顾名思义,typemap的目的是将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);
类型映射到C数据类型的匹配不仅仅是简单的文本匹配。事实上,类型映射完全内置在底层类型系统中。因此,typemaps不受typedef、名称空间和其他可能隐藏底层类型的声明的影响。例如,你可以有这样的代码:
/* 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仍然应用于适当的参数,即使typename并不总是匹配文本“int”。这种跟踪类型的能力是SWIG的一个关键部分——事实上,所有目标语言模块都只是为基本类型定义了一组类型映射。然而,从来没有必要为typedef引入的类型名编写新的类型映射。
除了跟踪类型名称之外,类型映射还可以专门化以匹配特定的参数名称。例如,你可以这样写一个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(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等等。
13.1.4 Reusing typemaps
类型映射通常是为特定的类型和参数名模式定义的。然而,typemaps也可以被复制和重用。一种方法是像这样使用赋值:
%typemap(in) Integer = int; %typemap(in) (char *buffer, int size) = (char *str, int len);
不过,有一种更强大的方法可以复制类型图系列。考虑以下两个类型映射方法家族,int类型的"in"和"out":
%typemap(in) int { /* Convert an integer argument */ ... } %typemap(out) int { /* Return an integer value */ ... }
对于size_t类型,两个typemap方法中的每一个都可以单独复制,如下所示:
/* Apply all of the int typemaps to size_t */ %typemap(in) size_t = int; %typemap(out) size_t = int;
%apply指令提供了一种更强大的复制形式。下面的代码与上面的代码相同:
/* Apply all of the int typemaps to size_t */ %apply int { size_t };
%apply仅仅接受为一个类型定义的所有类型映射,并将它们应用于其他类型。注意:你可以在{…}部分%应用。
typedef int size_t;
那么SWIG已经知道应用了int类型映射。你什么都不用做。
13.1.5 What can be done with typemaps?
typemaps的主要用途是在单个C/ c++数据类型级别上定义包装器生成行为。目前类型映射解决的问题有六类: