前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SWIG 官方文档第四部分 - 机翻中文人肉修正

SWIG 官方文档第四部分 - 机翻中文人肉修正

作者头像
韩伟
发布2021-09-03 15:45:24
5.2K0
发布2021-09-03 15:45:24
举报
文章被收录于专栏:韩伟的专栏韩伟的专栏

13 合约

包装 C 库时出现的一个常见问题是保持可靠性和检查错误。事实是,许多 C 程序因不提供错误检查而臭名昭著。不仅如此,当您将应用程序的内部结构公开为库时,通常可以通过提供错误的输入或以非预期的方式使用它而使其崩溃。

本章介绍 SWIG 对软件合同的支持。在 SWIG 的上下文中,合约可以被视为附加到声明的运行时约束。例如,您可以轻松附加参数检查规则、检查函数的输出值等。当脚本违反其中一项规则时,会生成运行时异常,而不是让程序继续执行。

13.1 %contract 指令

使用 %contract 指令将合同添加到声明中。这是一个简单的例子:

C++%contract sqrt(double x) {require: x >= 0;ensure: sqrt >= 0;} ...double sqrt(double);

在这种情况下,一个合约被添加到sqrt() 函数中。在%contract 指令必须有问题的声明之前始终出现。在合同中有两个部分,这两个部分都是可选的。的要求:必须持有被称为函数之前部分指定的条件。通常,这用于检查参数值。在保证:该功能后,必须持有部分指定条件被调用。这通常用于检查返回值或程序状态。在这两种情况下,必须满足的条件必须指定为布尔表达式。

在上面的例子中,我们只是确保 sqrt() 返回一个非负数(如果没有,那么它会以某种方式被破坏)。

一旦指定了合约,它就会修改结果模块的行为。例如:

Python>>> example.sqrt(2)1.4142135623730951>>> example.sqrt(-2)Traceback (most recent call last): File "<stdin>", line 1, in ?RuntimeError: Contract violation: require: (arg1>=0)>>>

13.2 %contract 和类

在%contract 指令也可以适用于类的方法和构造函数。例如:

C++%contract Foo::bar(int x, int y) {require: x > 0;ensure: bar > 0;} %contract Foo::Foo(int a) {require: a > 0;} class Foo {public: Foo(int); int bar(int, int);};

在这种方式里%contract 被应用是与%feature 指令完全一样的。因此,您为基类指定的任何 contract 也将附加到继承的方法。例如:

C++class Spam : public Foo {public: int bar(int, int); // Gets contract defined for Foo::bar(int, int)};

除此之外,单独的 contract 也可以应用于基类和派生类。例如:

C++%contract Foo::bar(int x, int) {require: x > 0;} %contract Spam::bar(int, int y) {require: y > 0;} class Foo {public: int bar(int, int); // Gets Foo::bar contract.}; class Spam : public Foo {public: int bar(int, int); // Gets Foo::bar and Spam::bar contract};

当应用多个合同时,“require:”部分中指定的条件使用逻辑与运算组合在一起。换句话说,为基类指定的条件和为派生类指定的条件都必须成立。在上面的例子中,这意味着 Spam::bar 的两个参数都必须是正数。

13.3 常量聚合和 %aggregate_check

考虑一个包含以下代码的接口文件:

C++#define UP 1#define DOWN 2#define RIGHT 3#define LEFT 4 void move(SomeObject *, int direction, int distance););

您可能想要做的一件事是对方向参数施加约束,以确保它是少数可接受的值之一。为此,SWIG 提供了一个易于使用的宏 %aggregate_check() ,其工作方式如下:

C++%aggregate_check(int, check_direction, UP, DOWN, LEFT, RIGHT);

这仅仅定义了形式的效用函数

C++int check_direction(int x);

这会检查参数 x 以查看它是否是列出的值之一。这个效用函数可以在合约中使用。例如:

C++%aggregate_check(int, check_direction, UP, DOWN, RIGHT, LEFT); %contract move(SomeObject *, int direction, in) {require: check_direction(direction);} #define UP 1#define DOWN 2#define RIGHT 3#define LEFT 4 void move(SomeObject *, int direction, int distance);

或者,它可以用于类型映射和其他指令。例如:

C++%aggregate_check(int, check_direction, UP, DOWN, RIGHT, LEFT); %typemap(check) int direction { if (!check_direction($1)) SWIG_exception(SWIG_ValueError, "Bad direction");} #define UP 1#define DOWN 2#define RIGHT 3#define LEFT 4 void move(SomeObject *, int direction, int distance);

遗憾的是,没有使用枚举值执行类似检查的自动方法。也许在未来的版本中。

13.4 注释

合约支持由 Songyan (Tiger) Feng 实现,首次出现在 SWIG-1.3.20。

14 可变长度参数

(又名,“恐怖。恐怖。”)

本章描述了包装带有可变数量参数的函数的问题。例如,为 C printf()系列函数生成包装器。

这个主题已经足够高级了,值得单独写一章。事实上,对可变参数的支持是一个经常被要求的特性,它最初是在 SWIG-1.3.12 中添加的。大多数其他包装器生成工具都明智地选择避免这个问题。

14.1 简介

某些 C 和 C++ 程序可能包含接受可变数量参数的函数。例如,大多数程序员都熟悉 C 库中的函数,例如:

C++int printf(const char *fmt, ...)int fprintf(FILE *, const char *fmt, ...);int sprintf(char *s, const char *fmt, ...);

尽管在脚本语言中包装这些特定的 C 库函数可能没有什么实际目的(这有什么意义?),但库可能包含自己的一组基于类似 API 的特殊函数。例如:

C++int traceprintf(const char *fmt, ...);

在这种情况下,您可能希望从目标语言获得某种访问权限。

在描述 SWIG 实现之前,重要的是讨论您在实际程序中可能会遇到的可变参数的常见用法。显然,有如图所示的 printf() 风格的输出函数。与此密切相关的是 scanf() 样式的输入函数,它接受格式字符串和放置返回值的指针列表。但是,有时也使用可变长度参数来编写接受以 NULL 结尾的指针列表的函数。一个很好的例子是这样的函数:

C++int execlp(const char *path, const char *arg1, ...);... /* 例子 */execlp("ls", "ls", "-l", NULL);

此外,varargs 有时用于伪造旧 C 库中的默认参数。例如,低级 open() 系统调用通常被声明为可变参数函数,以便它接受两个或三个参数:

C++int open(const char *path, int oflag, ...);... /* 例子 */f = open("foo", O_RDONLY);g = open("bar", O_WRONLY | O_CREAT, 0644);

最后,要实现可变参数函数,请记住您必须使用 <stdarg.h> 中定义的 C 库函数。例如:

C++List make_list(const char *s, ...) { va_list ap; List x; ... va_start(ap, s); while (s) { x.append(s); s = va_arg(ap, const char *); } va_end(ap); return x;}

14.2 问题

为可变长度参数函数生成包装器提出了许多特殊挑战。尽管 C 为实现接收可变长度参数的函数提供了支持,但没有任何函数可以反其道而行之。具体来说,您不能编写一个函数来动态创建参数列表并代表您调用 varargs 函数。

尽管可以编写接受特殊类型 va_list 的函数,但这是完全不同的。您不能采用 va_list 结构并将其代替可变长度参数传递给另一个可变参数函数。它只是不起作用。

这不起作用的原因与函数调用的编译方式有关。例如,假设您的程序有一个这样的函数调用:

C++printf("Hello %s. Your number is %d\n", name, num);

当编译器查看此内容时,它知道您正在使用正好三个参数调用 printf()。此外,它知道参数的数量以及它们的类型和大小在程序执行期间永远不会改变。因此,这变成了设置三参数堆栈帧的机器代码,然后调用 printf()。

相反,假设您尝试使用如下代码对 printf() 进行某种包装:

C++int wrap_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); ... printf(fmt, ap); ... va_end(ap);};

尽管此代码可能会编译,但它不会按照您的预期运行。这是因为对 printf() 的调用被编译为仅涉及两个参数的过程调用。但是,如果您的意图是将任意数量的参数传递给真正的 printf(),则调用堆栈的两个参数配置显然是完全错误的。不用说,这行不通。

不幸的是,刚刚描述的情况正是包装器生成工具面临的问题。通常,传递参数的数量直到运行时才会知道。更糟糕的是,直到运行时您才会知道参数的类型和大小。不用说,没有明显的方法可以让 C 编译器为涉及未知数量的未知类型参数的函数调用生成代码。

从理论上讲,它可以编写一个包装,做正确的事。但是,这涉及了解目标平台和语言的底层 ABI,以及编写在进行过程调用之前手动构建调用堆栈的特殊用途代码。不幸的是,这两项任务都需要使用内联汇编代码。显然,这是您宁愿避免的解决方案。

考虑到这一点,SWIG 为可变参数包装问题提供了许多解决方案。这些解决方案中的大多数都是折衷方案,它们提供有限的可变参数支持,而不必求助于汇编语言。但是,如果您愿意动手,SWIG 也可以支持真正的可变参数包装(使用堆栈帧操作)。继续阅读。

14.3 默认可变参数支持

当可变长度参数出现在接口中时,默认行为是完全删除可变参数列表,用单个 NULL 指针替换它们。例如,如果你有这个功能,

C++void traceprintf(const char *fmt, ...);

它将被包装,就好像它已声明如下:

C++void traceprintf(const char *fmt);

当在包装器内部调用该函数时,它的调用方式如下:

C++traceprintf(arg1, NULL);

可以说,这种方法似乎打败了可变长度参数的全部意义。然而,这实际上为许多简单类型的可变参数函数提供了足够的支持,仍然有用,但是它确实有一个警告。例如,您可以像这样(在 Python 中)进行函数调用:

Python>>> traceprintf("Hello World")>>> traceprintf("Hello %s. Your number is %d\n" % (name, num))>>> traceprintf("Your result is 90%%.")

注意字符串格式化是如何在 Python 而不是 C 中完成的。需要注意的是,传递的字符串必须在 C 中安全使用。例如,如果 name 包含一个“%”,它应该被双重转义以避免不可预测的行为:

Python>>> traceprintf("Your result is 90%.\n") # unpredictable behaviour>>> traceprintf("Your result is 90%%.\n") # good

请继续阅读以获取更多解决方案。

14.4 使用 %varargs 替换参数

不是删除可变长度参数,另一种方法是用一组合适的参数替换(...)。SWIG 提供了一个特殊的 %varargs 指令,可用于执行此操作。例如,

C++%varargs(int mode = 0) open;...int open(const char *path, int oflags, ...);

相当于:

C++int open(const char *path, int oflags, int mode = 0);

在这种情况下,%varargs 只是提供有关可能传递给函数的额外参数的更具体信息。如果 varargs 函数的参数是统一类型, %varargs 也可以接受数字参数计数,如下所示:

C++%varargs(3, char *str = NULL) execlp;...int execlp(const char *path, const char *arg, ...);

并被有效地视为:

int execlp(const char *path, const char *arg,

char *str1 = NULL,

char *str2 = NULL,

char *str3 = NULL);

这会将 execlp() 包装为一个最多接受 3 个可选参数的函数。根据应用的不同,这对于实际用途来说可能绰绰有余。

可以通过 compactdefaultargs 功能更改默认参数的处理。如果使用此功能,例如

C++%feature("compactdefaultargs") execlp;%varargs(3, char *str = NULL) execlp;...int execlp(const char *path, const char *arg, ...);

来自未提供最大参数数量的目标语言的调用,例如 execlp("a", "b","c") 将生成包含缺失默认值的 C 代码,即 execlp(" a", "b","c", NULL, NULL)。如果未使用 compactdefaultargs,则生成的代码将为execlp("a", "b", "c")。前者对于帮助提供终止参数列表的哨兵很有用。但是,这并不能保证,例如当用户为所有参数传递一个非 NULL 值时。使用 compactdefaultargs 时,可以保证 NULL 哨兵通过,最后一个参数。例如,

C++%feature("compactdefaultargs") execlp;%varargs(3, char *str = NULL) execlp;%typemap(in, numinputs=0) char *str3 ""...int execlp(const char *path, const char *arg, ...);

请注意,str3 是最后一个参数的名称,因为我们使用了 %varargs和 3。现在 execlp("a", "b", "c", "d", "e")将导致错误的为参数 1 传递了太多参数,因为现在只能传递 2 个额外的 'str' 参数,而第 3 个参数始终使用指定的默认NULL。

在额外参数的类型是统一的并且参数的最大数量已知的情况下,参数替换是最合适的。在处理接受混合参数类型(如printf() )的函数时,参数替换不是很有用。为这些函数提供通用包装会带来一些特殊问题(稍后会介绍)。

14.5 可变参数和类型映射

可变长度参数可用于类型映射规范。例如:

C++%typemap(in) (...) { // 获取可变长度参数(以某种方式) ...} %typemap(in) (const char *fmt, ...) { // 多参数类型映射}

然而,这立即引发了一个问题,即实际使用什么“类型”来表示(...)。由于缺乏更好的选择,(...)的类型设置为 void *。由于无法将参数动态传递给可变参数函数(如前所述),因此 void * 参数值旨在用作存储有关额外参数(如果有)的某种信息的占位符。此外,SWIG 的默认行为是将 void * 值作为参数传递给函数。因此,如果需要,您可以使用指针来保存有效的参数值。

为了说明这一点,这里是在 Python 中包装 printf() 的一个更安全的版本:

C++%typemap(in) (const char *fmt, ...) { $1 = "%s"; /* 将格式字符串固定为 %s */ $2 = (void *) PyString_AsString($input); /* 获取字符串参数 */};...int printf(const char *fmt, ...);

在此示例中,格式字符串被隐式设置为"%s" 。这可以防止程序将伪造的格式字符串传递给扩展。然后,传递的输入对象被解码并放置在为 (...) 参数定义的 void * 参数中。当进行实际的函数调用时,底层包装器代码将大致如下所示:

C++wrap_printf() { char *arg1; void *arg2; int result; arg1 = "%s"; arg2 = (void *) PyString_AsString(arg2obj); ... result = printf(arg1, arg2); ...}

请注意这两个参数是如何传递给函数的,它会执行您期望的操作。

下一个示例说明了一种更高级的可变参数类型映射。免责声明:这需要目标语言模块中的特殊支持,目前不能保证与所有 SWIG 模块一起使用。它还开始更普遍地说明支持可变参数的一些更基本的问题。

如果为任何形式的(...)定义了类型映射,许多 SWIG 模块将生成接受可变数量参数作为输入的包装器,并使这些参数以某种形式可用。这的确切细节取决于所使用的语言模块(有关更多详细信息,请参阅相应的章节)。但是,假设您想为前面显示的 execlp() 函数创建一个 Python 包装器。要使用 typemap 而不是使用 %varargs 来做到这一点,您可能首先编写这样的 typemap:

C++%typemap(in) (...)(char *vargs[10]) { int i; int argc; for (i = 0; i < 10; i++) vargs[i] = 0; argc = PyTuple_Size(varargs); if (argc > 10) { PyErr_SetString(PyExc_ValueError, "Too many arguments"); SWIG_fail; } for (i = 0; i < argc; i++) { PyObject *pyobj = PyTuple_GetItem(varargs, i); char *str = 0;%#if PY_VERSION_HEX>=0x03000000 PyObject *pystr; if (!PyUnicode_Check(pyobj)) { PyErr_SetString(PyExc_ValueError, "Expected a string"); SWIG_fail; } pystr = PyUnicode_AsUTF8String(pyobj); str = strdup(PyBytes_AsString(pystr)); Py_XDECREF(pystr);%#else if (!PyString_Check(pyobj)) { PyErr_SetString(PyExc_ValueError, "Expected a string"); SWIG_fail; } str = PyString_AsString(pyobj);%#endif vargs[i] = str; } $1 = (void *)vargs;} %typemap(freearg) (...) {%#if PY_VERSION_HEX>=0x03000000 int i; for (i = 0; i < 10; i++) { free(vargs$argnum[i]); }%#endif}

在“in”类型映射中,特殊变量 varargs 是一个元组,包含所有传递的额外参数(这是 Python 模块特有的)。然后类型映射将其分开并将值粘贴到字符串 args 数组中。然后,将数组分配给 $1(回想一下,这是对应于(...)的 void * 变量)。然而,这个分配只是图片的一半——显然仅凭这一点还不足以使函数工作。'freearg' 类型映射清除在 'in' 类型映射中分配的内存;生成此代码以在 execlp %feature 指令之后调用,如下所示:

C++%feature("action") execlp { char **vargs = (char **) arg3; result = execlp(arg1, arg2, vargs[0], vargs[1], vargs[2], vargs[3], vargs[4], vargs[5], vargs[6], vargs[7], vargs[8], vargs[9], NULL);} int execlp(const char *path, const char *arg, ...);

这将修补所有内容并创建一个或多或少有效的功能。但是,除非您确定他们已经喝了几杯咖啡,否则不要尝试向您的同事解释这一点。如果你真的想提升你的大师地位并增加你的工作保障,请继续下一部分。

14.6 使用 libffi 包装的可变参数

前面的所有示例都依赖于 SWIG 的可移植特性,这些特性不依赖于任何低级机器级细节。在许多方面,他们都通过将可变参数函数重铸为一些具有固定数量的已知类型参数的较弱变体,从而避免了可变长度参数的真正问题。在许多情况下,这工作得很好。但是,如果您想要比这更通用,则需要带出一些更大的枪支。

一种方法是使用特殊用途的库,例如 libffi ( http://www.sourceware.org/libffi/ )。libffi 是一个库,允许您以相对独立于平台的方式动态构建调用堆栈和调用过程。有关库的详细信息可以在 libffi 发行版中找到,此处不再赘述。

为了说明 libffi 的使用,假设您真的想为 execlp() 创建一个接受任意数量参数的包装器。为此,您可以对前面的示例进行一些调整。例如:

C++/* Take an arbitrary number of extra arguments and place into an array of strings */ %typemap(in) (...) { char **argv; int argc; int i; argc = PyTuple_Size(varargs); argv = (char **) malloc(sizeof(char *)*(argc+1)); for (i = 0; i < argc; i++) { PyObject *o = PyTuple_GetItem(varargs, i); if (!PyString_Check(o)) { free(argv); PyErr_SetString(PyExc_ValueError, "Expected a string"); SWIG_fail; } argv[i] = PyString_AsString(o); } argv[i] = NULL; $1 = (void *) argv;} /* Rewrite the function call, using libffi */ %feature("action") execlp { int i, vc; ffi_cif cif; ffi_type **types; void **values; char **args; vc = PyTuple_Size(varargs); types = (ffi_type **) malloc((vc+3)*sizeof(ffi_type *)); values = (void **) malloc((vc+3)*sizeof(void *)); args = (char **) arg3; /* Set up path parameter */ types[0] = &ffi_type_pointer; values[0] = &arg1; /* Set up first argument */ types[1] = &ffi_type_pointer; values[1] = &arg2; /* Set up rest of parameters */ for (i = 0; i <= vc; i++) { types[2+i] = &ffi_type_pointer; values[2+i] = &args[i]; } if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, vc+3, &ffi_type_uint, types) == FFI_OK) { ffi_call(&cif, (void (*)()) execlp, &result, values); } else { free(types); free(values); free(arg3); PyErr_SetString(PyExc_RuntimeError, "Whoa!!!!!"); SWIG_fail; } free(types); free(values); free(arg3);} /* Declare the function. Whew! */int execlp(const char *path, const char *arg1, ...);

看着这个例子,您可能会开始怀疑 SWIG 是否让生活变得更轻松了。考虑到所涉及的代码量,您可能还想知道为什么不直接编写一个手工制作的包装器!要么是那个,要么你想知道“我到底为什么要尝试包装这个可变参数函数?!?” 显然,这些是你必须自己回答的问题。

作为 libffi 的一个更极端的例子,这里是一些试图包装printf() 的代码,

C++/* A wrapper for printf() using libffi */ %{/* Structure for holding passed arguments after conversion */ typedef struct { int type; union { int ivalue; double dvalue; void *pvalue; } val; } vtype; enum { VT_INT, VT_DOUBLE, VT_POINTER };%} %typemap(in) (const char *fmt, ...) { vtype *argv; int argc; int i; /* Format string */ $1 = PyString_AsString($input); /* Variable length arguments */ argc = PyTuple_Size(varargs); argv = (vtype *) malloc(argc*sizeof(vtype)); for (i = 0; i < argc; i++) { PyObject *o = PyTuple_GetItem(varargs, i); if (PyInt_Check(o)) { argv[i].type = VT_INT; argv[i].val.ivalue = PyInt_AsLong(o); } else if (PyFloat_Check(o)) { argv[i].type = VT_DOUBLE; argv[i].val.dvalue = PyFloat_AsDouble(o); } else if (PyString_Check(o)) { argv[i].type = VT_POINTER; argv[i].val.pvalue = (void *) PyString_AsString(o); } else { free(argv); PyErr_SetString(PyExc_ValueError, "Unsupported argument type"); return NULL; } } $2 = (void *) argv;} /* Rewrite the function call using libffi */ %feature("action") printf { int i, vc; ffi_cif cif; ffi_type **types; void **values; vtype *args; vc = PyTuple_Size(varargs); types = (ffi_type **) malloc((vc+1)*sizeof(ffi_type *)); values = (void **) malloc((vc+1)*sizeof(void *)); args = (vtype *) arg2; /* Set up fmt parameter */ types[0] = &ffi_type_pointer; values[0] = &arg1; /* Set up rest of parameters */ for (i = 0; i < vc; i++) { switch(args[i].type) { case VT_INT: types[1+i] = &ffi_type_uint; values[1+i] = &args[i].val.ivalue; break; case VT_DOUBLE: types[1+i] = &ffi_type_double; values[1+i] = &args[i].val.dvalue; break; case VT_POINTER: types[1+i] = &ffi_type_pointer; values[1+i] = &args[i].val.pvalue; break; default: abort(); /* Whoa! We're seriously hosed */ break; } } if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, vc+1, &ffi_type_uint, types) == FFI_OK) { ffi_call(&cif, (void (*)()) printf, &result, values); } else { free(types); free(values); free(args); PyErr_SetString(PyExc_RuntimeError, "Whoa!!!!!"); SWIG_fail; } free(types); free(values); free(args);} /* The function */int printf(const char *fmt, ...);

令您惊讶的是,如果您尝试一下,它甚至似乎有效:

Python>>> import example>>> example.printf("Grade: %s %d/60 = %0.2f%%\n", "Dave", 47, 47.0*100/60)Grade: Dave 47/60 = 78.33%>>>

当然,仍有一些限制需要考虑:

Python>>> example.printf("la de da de da %s", 42)Segmentation fault (core dumped)

并且,在此说明中,我们将进一步探索 libffi 作为练习留给读者。尽管以 Python 为例,本节中的大多数技术都可以通过一些工作来外推到其他语言模块。您需要知道的唯一细节是如何在每种目标语言中访问额外的参数。例如,在 Python 模块中,我们使用特殊的varargs 变量来获取这些参数。诸如 Tcl8 和 Perl5 之类的模块只是为第一个额外参数提供一个参数编号。这可用于索引传递参数的数组以获取值。有关详细信息,请参阅每个语言模块的章节。

14.7 va_list 的包装

与可变长度参数包装密切相关,您可能会遇到接受va_list类型参数的函数。例如:

C++int vprintf(const char *fmt, va_list ap);

据我们所知,没有明显的方法可以用 SWIG 包装这些函数。这是因为没有记录的方法来组装正确的 va_list 结构(没有 C 库函数可以做到这一点,并且 va_list 的内容是不透明的)。不仅如此, va_list结构的内容与底层调用堆栈紧密相关。目前尚不清楚导出 va_list 是否有任何用处或它是否会起作用。

解决方法可以通过编写一个简单的可变参数 C 包装器,然后使用本章前面讨论的可变参数技术来实现。下面是重命名的 vprintf 的一个简单包装器,以便它仍然可以从您的目标语言中称为 vprintf。示例中使用的 %vararg s 将函数限制为采用一个字符串参数。

C++%{int vprintf(const char *fmt, va_list ap);%} %varargs(const char *) my_vprintf;%rename(vprintf) my_vprintf; %inline %{int my_vprintf(const char *fmt, ...) { va_list ap; int result; va_start(ap, fmt); result = vprintf(fmt, ap); va_end(ap); return result;}%}

14.8 C++ 问题

包装接受可变数量参数的 C++ 成员函数提出了许多挑战。到目前为止,处理这个问题的最简单方法是使用 %varargs 指令。这是可移植的,它完全支持类似于 %rename 指令的类。例如:

C++%varargs (10, char * = NULL) Foo::bar; class Foo {public: virtual void bar(char *arg, ...); // gets varargs above}; class Spam: public Foo {public: virtual void bar(char *arg, ...); // gets varargs above};

%varargs 也适用于构造函数、运算符和任何其他接受可变参数的 C++ 编程构造。

做任何比这更高级的事情很可能会带来严重的痛苦。为了使用像 libffi 这样的库,您需要了解 C++ ABI 的底层调用约定和详细信息。例如,详细说明这是如何传递给成员函数以及可能用于传递附加信息的任何隐藏参数。这些细节是特定于实现的,可能因编译器甚至同一编译器的不同版本而异。另外,请注意,如果成员函数是虚方法,则调用成员函数会更加复杂。在这种情况下,调用可能需要查找表来获取正确的函数地址(尽管您可以通过将绑定指针转换为指向函数的指针来获取地址,如 C++ ARM 部分 18.3.4 中所述)。

如果您决定更改底层操作代码,请注意 SWIG 始终将this指针放在arg1 中。其他参数放在arg2、arg3等中。例如:

C++%feature("action") Foo::bar { ... result = arg1->bar(arg2, arg3, etc.); ...}

考虑到有可能让自己陷入困境,与创建可变参数 C++ 成员函数的完全通用包装器相比,重新考虑您的设计或使用辅助函数提供替代接口可能更容易。

14.9 讨论

本章提供了许多可用于解决可变长度参数包装问题的技术。如果您关心可移植性和易用性,%varargs指令可能是解决问题的最简单方法。但是,使用类型映射,可以进行一些非常高级的包装。

一个讨论点涉及上一节中 libffi 示例的结构。查看该代码,完全不清楚这是解决问题的最简单方法。然而,解决方案有许多微妙的方面需要考虑——主要是关于问题的分解方式。首先,该示例的结构方式试图在特定于包装器的信息和函数本身的声明之间保持分离。这里的想法是你可以像这样构建你的界面:

C++%typemap(const char *fmt, ...) { ...}%feature("action") traceprintf { ...} /* 在其中包含一些带有 traceprintf 的头文件 */%include "someheader.h"

其次,仔细审查会发现涉及 (...)的类型映射与 libffi 库没有任何关系。事实上,就函数实际调用的方式而言,它们是通用的。这种解耦意味着考虑其他库替代方案来进行函数调用会容易得多。例如,如果某个平台不支持 libffi,您可能可以使用其他东西来代替。您可以使用条件编译来控制这一点:

C++#ifdef USE_LIBFFI%feature("action") printf { ...}#endif#ifdef USE_OTHERFFI%feature("action") printf {...}#endif

最后,即使您可能倾向于只为可变参数函数编写一个手写的包装器,但上一节中使用的技术具有与 SWIG 的所有其他功能(例如异常处理)兼容的优点。

最后,一些 C 程序员似乎假设可变长度参数函数的包装是一个容易解决的问题。然而,本节有希望消除其中的一些神话。在所有条件相同的情况下,如果可以,最好避免可变长度参数。如果您无法避免它们,请先考虑一些简单的解决方案。如果您不能接受一个简单的解决方案,请谨慎行事。至少,请确保您仔细阅读 Kernighan 和 Ritchie 中的“A7.3.2 函数调用”部分,并确保您完全理解用于可变参数的参数传递约定。此外,请注意这将引入的平台依赖性和可靠性问题。祝你好运。

15 警告信息

15.1 简介

在编译期间,SWIG 可能会生成各种警告消息。例如:

Plain Textexample.i:16: Warning 501: Overloaded declaration ignored. bar(double)example.i:15: Warning 501: Previous declaration is bar(int)

通常,警告消息指示输入的非致命问题,其中生成的包装器代码可能会编译,但它可能不会像您期望的那样工作。

15.2 警告消息抑制

所有警告消息都有一个数字代码,显示在警告消息本身中。为了禁止打印警告消息,可以使用多种技术。首先,您可以使用 -w命令行选项运行 SWIG 。例如:

Plain Text% swig -python -w501 example.i% swig -python -w501,505,401 example.i

或者,可以通过在输入文件中插入一个特殊的预处理器编译指示来抑制警告:

C++%module example#pragma SWIG nowarn=501#pragma SWIG nowarn=501,505,401

最后,可以使用%warnfilter指令在逐个声明的基础上禁用代码生成警告。例如:

C++%module example%warnfilter(501) foo;...int foo(int);int foo(double); // Silently ignored.

在%warnfilter指令有相同的语义其他的声明修饰语喜欢%的重命名,%无视和%的功能,请参阅%的功能指令节。例如,如果您想取消对类层次结构中方法的警告,您可以这样做:

C++%warnfilter(501) Object::foo;class Object {public: int foo(int); int foo(double); // Silently ignored ...}; class Derived : public Object {public: int foo(int); int foo(double); // Silently ignored ...};

可以通过提供类名来抑制整个类的警告。例如:

C++%warnfilter(501) Object; class Object {public: ... // All 501 warnings ignored in class};

没有选项可以取消所有 SWIG 警告消息。警告消息的存在是有原因的——告诉您界面中的某些内容可能已损坏。忽略警告消息后果自负。

15.3 启用额外警告

某些警告消息在默认情况下处于禁用状态,生成仅用于提供额外的诊断。可以使用 -Wextra 选项打开这些警告。例如:

Plain Text% swig -Wextra -python example.i

要有选择地打开额外的警告消息,您可以使用上一节中的指令和选项——只需在所有警告编号上添加一个“+”即可。例如:

Plain Text% swig -w+309,+452 example.i

或在您的界面文件中使用

C++#pragma SWIG nowarn=+309,+452

要么

C++%warnfilter(+309,+452) foo;

注意:使用 %warnfilter 选择性启用警告会覆盖您可能使用 -w 或 #pragma 进行的任何全局设置。

您当然也可以启用所有警告并禁止选择一些警告,例如:

Plain Text% swig -Wextra -w309,452 example.i

右边的警告优先于左边的警告,所以在上面的例子中 -Wextra 添加了许多警告,包括 452,但是 -w309,452 覆盖了这个,所以 452 被抑制。

如果您希望显示所有警告,而不管使用的警告过滤器如何,请使用 -Wall 选项。该 -Wall 选项还打开了额外的警告,-Wextra 增加,但是,它是 subtely 不同。使用 -Wall 时,它还会禁用所有其他警告过滤器,即在 %warnfilter、#pragma SWIG nowarn 或 -w选项中抑制或添加的任何警告。

15.4 发出警告信息

可以使用许多指令从接口文件发出警告消息。在%警告指令是最简单的:

C++%warn "900:This is your last warning!"

所有警告消息都可选地以要使用的警告编号为前缀。如果您自己生成警告,请确保不要使用本节末尾的表格中定义的数字。

在%ignorewarn 指令相同%ignore ,除了它发出每当匹配的声明中警告消息。例如:

C++%ignorewarn("362:operator= ignored") operator=;

警告消息可以使用类型映射声明的警告属性与类型映射相关联。例如:

C++%typemap(in, warning="901:You are really going to regret this usage of $1_type $1_name") blah * { ...}

在这种情况下,每当实际使用类型映射时都会打印警告消息,并且将适当扩展特殊变量,例如:

Plain Textexample.i:23: Warning 901: You are really going to regret this usage of blah * selfexample.i:24: Warning 901: You are really going to regret this usage of blah * stuff

15.5 符号

与 SWIG 一起安装的 swigwarn.swg 文件包含也可用于 %warnfilter 和 #pragma SWIG nowarn 的符号常量。例如,此文件包含以下行:

C++%define SWIGWARN_TYPE_UNDEFINED_CLASS 401 %enddef

所以可以使用 SWIGWARN_TYPE_UNDEFINED_CLASS 代替 401,例如:

C++#pragma SWIG nowarn=SWIGWARN_TYPE_UNDEFINED_CLASS

要么

C++%warnfilter(SWIGWARN_TYPE_UNDEFINED_CLASS) Foo;

15.6 评论

抑制警告消息的能力实际上只提供给高级用户,不建议在正常使用中使用。建议您修改您的界面以尽可能修复警告突出显示的问题,而不是抑制警告。

某些类型的 SWIG 问题是错误。这些通常是由于解析错误(错误的语法)或没有明显恢复的语义问题引起的。没有抑制错误消息的机制。

15.7 作为错误的警告

可以使用 -Werror 命令行选项将警告作为错误处理。如果遇到警告,这将导致 SWIG 以不成功的退出代码退出。

15.8 消息输出格式

可以选择警告和错误的输出格式以与您喜欢的 IDE/编辑器集成。编辑器和 IDE 通常可以解析错误消息,如果采用适当的格式,将很容易将您直接带到错误的源头。默认情况下使用标准格式,但在默认情况下使用 Microsoft 格式的 Windows 上除外。这些可以使用命令行选项覆盖,例如:

Plain Text$ swig -python -Fstandard example.iexample.i:4: Syntax error in input(1).$ swig -python -Fmicrosoft example.iexample.i(4) : Syntax error in input(1).

15.9 警告编号参考

15.9.1 过时的特性 (100-199)

• 101. 不推荐使用%extern指令。

• 102. 不推荐使用%val指令。

• 103. 不推荐使用%out指令。

• 104. 不推荐使用%disabledoc指令。

• 105. 不推荐使用%enabledoc指令。

• 106. 不推荐使用%doconly指令。

• 107. 不推荐使用%style指令。

• 108. 弃用%localstyle指令。

• 109. 弃用%title指令。

• 110. 不推荐使用%section指令。

• 111. 不推荐使用%subsection指令。

• 112. 不推荐使用%subsubsection指令。

• 113. 不推荐使用%addmethods指令。

• 114. 弃用%readonly指令。

• 115. 不推荐使用%readwrite指令。

• 116. 不推荐使用%except指令。

• 117. 不推荐使用%new指令。

• 118. 弃用%typemap(except)。

• 119. 弃用%typemap(ignore)。

• 120. 不推荐使用的命令行选项(-runtime、-noruntime)。

• 121. 不推荐使用%name指令。

• 126. 'nestedworkaround' 功能已被弃用。

15.9.2 预处理器 (200-299)

• 201. 无法找到文件名

• 202. 无法计算表达式expr

• 203. includeall 和importall 都定义:使用includeall。

• 204. CPP #warning,“警告”。

• 205. CPP #error,“错误”。

• 206. #指令后出现意外标记。

15.9.3 C/C++ 解析器 (300-399)

• 301. 使用了class关键字,但不在 C++ 模式下。

• 302. 标识符“名称”重新定义(忽略)。

• 303.为未声明的类“名称 ”定义了%extend。

• 304. 不支持的常量值(忽略)。

• 305. 错误的常量值(忽略)。

• 306.在此上下文中,“标识符”是私有的。

• 307. 不能设置默认参数值(忽略)

• 308.此处不允许命名空间别名“ name ”。假设 ' name '

• 309.【私| protected] 继承被忽略。

• 310. 模板 ' name ' 已经被包装为 ' name '(被忽略)

• 312. 当前不支持(忽略)未命名的嵌套类。

• 313. Unrecognized extern type " name" (ignored)。

• 314. ' identifier ' 是一个lang关键字。

• 315. 对“标识符”一无所知。

• 316. 重复 %module 指令。

• 317. 非模板' name '的特化。

• 318实例化的模板“”不明确,实例TEMPL使用实例TEMPL忽略。

• 319. 没有为基类名称提供访问说明符(忽略)。

• 320. 显式模板实例化被忽略。

• 321.标识符与内置名称冲突。

• 322. ' name ' 的冗余重新声明。

• 323. ' name ' 的递归范围继承。

• 324. 不支持命名嵌套模板实例化。就好像没有给%template() 命名一样进行处理。

• 325.当前不支持嵌套种类(忽略名称)。

• 326. 不推荐使用 %extend name -应该使用种类名称“ name ”而不是 typedef 名称“ name ”。

• 350. operator new 被忽略。

• 351. 操作符删除被忽略。

• 352. operator+ 被忽略。

• 353. operator- 被忽略。

• 354. operator* 被忽略。

• 355. 运算符/被忽略。

• 356. 运算符 % 被忽略。

• 357. operator^ 被忽略。

• 358. operator& 被忽略。

• 359.算子| 忽略。

• 360.operator~被忽略。

• 361.算!忽略。

• 362. operator= 被忽略。

• 363.运算符<被忽略。

• 364.运营商>忽略。

• 365. operator+= 被忽略。

• 366. operator-= 被忽略。

• 367.运算符*=被忽略。

• 368. operator/= 被忽略。

• 369. 运算符 %= 被忽略。

• 370. 运算符^= 被忽略。

• 371. operator&= 被忽略。

• 372. 运算符|= 被忽略。

• 373.运算符<<被忽略。

• 374. 运营商>>忽略。

• 375. 运算符<<= 被忽略。

• 376. 运算符>>= 被忽略。

• 377. operator== 被忽略。

• 378. operator!= 被忽略。

• 379. operator<= 被忽略。

• 380. operator>= 被忽略。

• 381.运算符&&被忽略。

• 382.运算符|| 忽略。

• 383. operator++ 被忽略。

• 384. 运算符——被忽略。

• 385.算子,无视。

• 386. 运算符-<* 被忽略。

• 387. 运算符-< 被忽略。

• 388. operator() 被忽略。

• 389. 运算符 [] 被忽略。

• 390. operator+ 被忽略(一元)。

• 391. operator- 忽略(一元)。

• 392. operator* 被忽略(一元)。

• 393. operator& 被忽略(一元)。

• 394. 运算符 new[] 被忽略。

• 395. 操作符 delete[] 被忽略。

15.9.4 类型和类型映射 (400-499)

• 401. 对类“名称”一无所知。忽略。

• 402. 基类“名称”不完整。

• 403. 类 'name' 可能是抽象的。

• 450. 不推荐使用的类型映射功能(source/target)。

• 451. 设置 const char * 变量可能会泄漏内存。

• 452.保留

• 453.不能申请(模式)。没有定义类型映射。

• 460. 无法使用类型类型作为函数参数。

• 461. 无法在函数名中使用返回类型类型

• 462. 无法设置类型为type 的变量。

• 463. 无法读取 type类型的变量。

• 464. 不支持的常量值。

• 465. 无法处理类型type

• 466. 不支持的变量类型type

• 467.不支持重载声明(不完整的类型检查规则 - ' type' 的类型检查类型映射中没有优先级)

• 468.没有“扔”类型映射为异常类型定义的类型

• 469. 没有或不正确的为type定义的directorin typemap

• 470. 线程/可重入不安全包装,请考虑按值返回。

• 471. 无法在director 方法中使用返回类型类型

• 474.方法方法在出类型映射为以下忽略的最佳属性的使用不能被用来产生最佳的代码:代码

• 475.由于 out 类型映射中的最佳属性使用,可能会生成对方法的多次调用。

• 476. 使用std::initializer_list 初始化。

• 477. 没有为类型定义的directorthrows typemap

15.9.5 代码生成 (500-599)

• 501. 忽略重载声明。东方电气。先前的声明是decl

• 502. 重载的构造函数被忽略。东方电气。先前的声明是decl

• 503. 不能包装“标识符”,除非重命名为有效标识符。

• 504. 函数必须有返回类型。忽略。

• 505. 丢弃可变长度参数。

• 506. 不能在启用关键字参数的情况下包装可变参数。

• 507.不支持(忽略)添加本地函数名称

• 508. ' name ' 的声明隐藏了可通过 operator->() 访问的声明,' declaration' 的先前声明。

• 509. 重载的方法声明被有效地忽略,因为它被声明遮蔽了。

• 510. 友元函数 ' name ' 被忽略。

• 511. 不能对重载函数使用关键字参数。

• 512. 重载方法声明被忽略,使用非常量方法声明代替。

• 513. 无法为未命名的结构/类生成包装器。

• 514.

• 515.

• 516. 重载方法声明被忽略,使用声明代替。

• 517.

• 518. 可移植性警告:在不区分大小写的文件系统(如 Windows 的 FAT32 和 NTFS)上,文件file1将被 file2覆盖,除非类/模块名称被重命名。

• 519. %template() 不包含名称。忽略的模板方法:声明

• 520.基材/派生类“ classname1”的“ classname2 ”未类似地标记为智能指针。

• 521. 非法析构函数名称name。忽略。

• 522.不推荐在 %extend 中使用非法的构造函数名称“ name ”,构造函数名称应为“ name ”。

• 523.不赞成在 %extend 中使用非法的析构函数名称“ name ”,析构函数名称应为“ name ”。

15.9.6 特定语言模块 (700-899)

• 801. 名称错误(更正为'名称')。(红宝石)。

• 810. 没有为类型(Java)定义 jni类型映射。

• 811. 没有为类型(Java)定义 jtype类型映射。

• 812. 没有为类型(Java)定义 jstype类型映射。

• 813.类名警告,基被忽略。Java 不支持多重继承。(爪哇)。

• 814.

• 815. 没有为类型(Java)定义的 javafinalize类型映射。

• 816. 没有为类型(Java)定义的 javabody类型映射。

• 817. 没有为类型(Java)定义 javaout类型映射。

• 818. 没有为类型(Java)定义的 javain类型映射。

• 819. 没有为类型(Java)定义的 javadirectorin类型映射。

• 820. 没有为类型(Java)定义的 javadirectorout类型映射。

• 821.

• 822. Java 不支持协变返回类型。代理方法将返回basetype (Java)。

• 823. 没有为类型(Java)定义的 javaconstruct类型映射。

• 824. 在为类型(Java)定义的导演类型映射中缺少 JNI 描述符。

• 825.类型 “javaconstruct”类型映射中缺少“directorconnect”属性。(爪哇)。

• 826. nspace 功能用于没有-package 的“类型”。生成的代码可能无法编译,因为 Java 不支持命名包中声明的类型访问未命名包中声明的类型。(爪哇)。

• 830. 没有为类型(C#)定义 ctype类型映射。

• 831. 没有为类型(C#)定义 cstype类型映射。

• 832. 没有为类型(C#)定义 cswtype类型映射。

• 833. classname警告,base baseclass被忽略。C# 不支持多重继承。(C#)。

• 834.

• 835. 没有为类型(C#)定义的 csfinalize类型映射。

• 836. 没有为类型(C#)定义 csbody类型映射。

• 837. 没有为类型(C#)定义 csout类型映射。

• 838. 没有为类型(C#)定义 csin类型映射。

• 839.

• 840.

• 841.

• 842. C# 不支持协变返回类型。代理方法将返回basetype (C#)。

• 843. 没有为类型(C#)定义 csconstruct类型映射。

• 844. 可能不会抛出 C# 异常 - typemaptypemap 中没有 $excode 或 excode 属性。(C#)。

• 845. 非托管代码包含对SWIG_CSharpSetPendingException 方法的调用,并且 C# 代码不通过 canthrow 属性处理挂起的异常。(C#)。

• 870.类名警告:基被忽略。PHP 不支持多重继承。(PHP)。

• 871. Unrecognized pragma pragma .(PHP)。

15.9.7 用户定义 (900-999)

您自己的应用程序可以使用这些数字。

15.10 历史

SWIG-1.3.12 首次添加了控制警告消息的功能。

16 使用模块

16.1 模块介绍

每次调用 SWIG 都需要指定一个模块名称。模块名称用于命名生成的目标语言扩展模块。这究竟意味着什么以及名称的用途取决于目标语言,例如,名称可以定义目标语言命名空间,或者仅仅是用于命名文件或帮助程序类的有用名称。本质上,一个模块包含目标语言包装器,用于选定全局变量/函数、结构/类和其他 C/C++ 类型的集合。

可以通过以下两种方式之一提供模块名称。第一个是使用特殊的 %module 指令指定它。该指令必须出现在接口文件的开头。该指令的一般形式是:

C++%module(option1="value1", option2="value2", ...) modulename

其中模块名是强制性的,选项添加了一个或多个可选的附加功能。通常不指定选项,例如:

C++%module mymodule

指定模块名称的第二种方法是使用 -module 命令行选项,例如 -module mymodule。如果在命令行上提供了模块名称,它将覆盖 %module 指令指定的名称。

首次使用 SWIG 时,用户通常从创建单个模块开始。也就是说,您可以定义单个 SWIG 接口来包装一些 C/C++ 代码集。然后将所有生成的包装器代码编译在一起并使用它。然而,对于大型应用程序,这种方法是有问题的——生成的包装器代码的大小可能相当大。此外,将目标语言界面分解成更小的部分可能更容易管理。

本章描述了在要创建模块集合的程序中使用 SWIG 的问题。集合中的每个模块都是通过单独调用 SWIG 创建的。

16.2 基础知识

多个模块的基本用例是模块没有交叉引用(即包装多个独立的 C API 时)。在这种情况下,swig 输入文件应该是开箱即用的——您只需创建多个包装 .cxx 文件,将它们链接到您的应用程序中,然后在脚本语言运行时中插入/加载每个文件,就像您在单个模块案例中所做的那样。

更复杂的是模块需要共享信息的情况。例如,当一个模块通过派生来扩展另一个模块的类时:

C++// File: base.hclass base {public: int foo();};

C++// File: base_module.i%module base_module %{#include "base.h"%}%include "base.h"

C++ // File: derived_module.i%module derived_module %import "base_module.i" %inline %{class derived : public base {public: int bar();};%}

为了正确地创建包装器,模块 derived_module 需要知道基类并且它的接口被另一个模块覆盖。%import "base_module.i" 这一行让 SWIG 知道这一点。通常 .h 文件被传递给 %import 而不是.i,不幸的是它不适用于所有语言模块。例如,Python 需要基类所在模块的名称,以便代理类可以完全继承基类的方法。通常,当缺少模块名称时,您会收到警告,例如:

Plain Textderived_module.i:8: Warning 401: Base class 'base' ignored - unknown module name for base. Eitherimportthe appropriate module interface file or specify the name of the module in the %import directive.

有时需要导入头文件而不是接口文件并克服上述警告。例如,在导入的接口非常大的情况下,可能需要简化问题,只导入依赖类型的小头文件。这可以通过在 %import 指令中指定可选的模块属性来完成。上面显示的derived_module.i 文件可以替换为以下内容:

C++// File: derived_module.i%module derived_module %import(module="base_module") "base.h" %inline %{class derived : public base {public: int bar();};

请注意,“base_module” 是和模块名称一样的,指定%module 在 base_module.i 还有%import 在 derived_module.i 里。

要注意的另一个问题是,不应从多个线程并行链接/加载多个依赖包装器,因为 SWIG 不提供锁定 - 有关该问题的更多信息,请继续阅读。

16.3 SWIG 运行时代码

许多 SWIG 的目标语言都会生成一组通常称为“SWIG 运行时”的函数。这些函数主要与运行时类型系统相关,该系统检查指针类型并执行其他任务,例如在 C++ 中正确转换指针值。作为一般规则,静态类型的目标语言(例如 Java)使用该语言的内置静态类型检查,并且不需要 SWIG 运行时。所有动态类型/解释语言都依赖于 SWIG 运行时。

运行时函数对于每个 SWIG 生成的模块都是私有的。也就是说,运行时函数是用“静态”链接声明的,并且仅对在该模块中定义的包装函数可见。这种方法的唯一问题是,当同一个应用程序中使用多个 SWIG 模块时,这些模块通常需要共享类型信息。对于 C++ 程序尤其如此,其中 SWIG 必须收集和共享有关跨模块边界的继承关系的信息。

为了解决跨模块共享信息的问题,指向类型信息的指针存储在目标语言命名空间的全局变量中。在模块初始化期间,类型信息从所有模块加载到类型信息的全局数据结构中。

这种方法有一些权衡。此类型信息在加载的所有 SWIG 模块中是全局的,并且可能导致未设计为协同工作的模块之间的类型冲突。为了解决这种方法,SWIG 运行时代码使用定义 SWIG_TYPE_TABLE 来提供唯一的类型表。在编译生成的 _wrap.cxx 或 _wrap.c 文件时,可以通过将 -DSWIG_TYPE_TABLE=myprojectname 添加到命令行参数来启用此行为。

然后,只有使用设置为 myprojectname 的 SWIG_TYPE_TABLE 编译的模块才会共享类型信息。所以如果你的项目有三个模块,三个模块都应该用 -DSWIG_TYPE_TABLE=myprojectname 编译,然后这三个模块会共享类型信息。但是任何其他项目的类型都不会干扰或与您模块中的类型发生冲突。

另一个与全局类型表相关的问题是线程安全。如果两个模块同时尝试加载,则类型信息可能会损坏。SWIG 目前不提供任何锁定,如果使用线程,则必须确保模块是串行加载的。如果您使用线程和某些脚本语言提供的自动模块加载,请小心。一种解决方案是在产生任何线程之前加载所有模块,或者使用 SWIG_TYPE_TABLE 来分隔类型表,这样它们就不会相互冲突。

最后,SWIG 使用 #define SWIG_RUNTIME_VERSION,位于 Lib/swigrun.swg 并靠近每个生成模块的顶部。当数据结构发生变化时,这个数字会增加,以便不同版本生成的 SWIG 模块可以和平共存。因此,类型结构由 (SWIG_TYPE_TABLE, SWIG_RUNTIME_VERSION) 对分隔,其中默认情况下 SWIG_TYPE_TABLE 为空。只有使用同一对编译的模块才会共享类型信息。

16.4 运行时的外部访问

如运行时类型检查器中所述,有时需要调用函数 SWIG_TypeQuery、 SWIG_NewPointerObj 和其他函数。支持从类型映射调用这些函数,因为类型映射代码嵌入到 _wrap.c文件中,该文件具有可用的这些声明。如果您需要从另一个 C 文件调用 SWIG 运行时函数,则需要包含一个头文件。为了生成需要包含的头文件,SWIG 可以通过-external-runtime 以不同的模式运行以生成运行时,而不是处理输入接口文件的正常模式。例如:

Plain Text$ swig -python -external-runtime <filename>

filename 参数是可选的,如果未传递,则默认文件名将类似于 swigpyrun.h,具体取决于语言。这个头文件应该像任何其他 _wrap.c 输出文件一样对待,并且应该在 _wrap 文件时重新生成。包含此标头后,您的代码将能够调用 SWIG_TypeQuery、SWIG_NewPointerObj、SWIG_ConvertPtr 等。这些函数的确切参数参数可能因语言模块而异;请查看语言模块章节以获取更多信息。

在这个头文件中,函数被声明为静态并被内联包含在文件中,因此文件不需要链接到任何 SWIG 库或代码(您可能仍然需要链接到 libpython-2.3 之类的语言库)。数据通过脚本语言中的全局变量在此文件和 _wrap.c 文件之间共享。也可以将这个头文件连同生成的包装文件一起复制到你自己的包中,这样你就可以分发一个可以在没有安装 SWIG 的情况下编译的包(这是可行的,因为头文件是自包含的,不需要链接任何东西)。

此头文件还将使用上述 -DSWIG_TYPE_TABLE,因此在编译包含生成的头文件的任何代码时,应将 SWIG_TYPE_TABLE 定义为与您尝试访问其类型的模块相同。

16.5 关于静态库的警告

使用多个 SWIG 模块时,应注意不要使用静态库。例如,如果您有一个静态库 libfoo.a 并且您将一组 SWIG 模块与该库链接,则每个模块都将获得自己插入的库代码的私有副本。这通常不是您想要的,它可能会导致意外或奇怪的程序行为。使用可动态加载的模块时,您应该尝试专门使用共享库。

16.6 参考文献

由于使用共享库和多个模块的复杂性,咨询外部参考可能是个好主意。强烈推荐 John Levine 的“Linkers and Loaders”。

16.7 减少包装文件大小

将多个模块与 %import 指令一起使用是模块化大型项目的最常用方法。通过这种方式,可以生成多个不同的包装文件,从而避免生成单个大包装文件。通过使用命令行选项和功能来减小包装器文件的大小有几种替代解决方案。

-fcompact

此命令行选项将压缩包装文件的大小,而不会更改生成到包装文件中的代码。它只是删除空行并将代码行连接在一起。这对于具有可以处理的最大文件大小的编译器很有用。

-fvirtual

此命令行选项将删除多余的虚拟方法包装器的生成。考虑以下继承层次结构:

C++struct Base { virtual void method(); ...}; struct Derived : Base { virtual void method(); ...};

通常为这两种方法生成包装器,而此命令行选项将禁止为 Derived::method 生成包装器。普通多态遗存作为 Derived::method 仍然会叫你应该有一个派生的实例,并要求包装 Base::method。

%feature("compactdefaultargs")

当使用默认参数包装方法时,此功能可以减少包装方法的数量。默认参数部分讨论了该功能及其限制。

28 SWIG 和 Lua

Lua 是一种扩展编程语言,旨在支持具有数据描述功能的通用过程编程。它还为面向对象编程、函数式编程和数据驱动编程提供了良好的支持。Lua 旨在用作任何需要它的程序的强大、轻量级配置语言。Lua 被实现为一个库,用干净的 C 编写(即在 ANSI C 和 C++ 的公共子集中)。它也是一种非常小的语言,不到 6000 行代码,可编译为 <100 KB 的二进制代码。它可以在 http://www.lua.org上找到

eLua 代表嵌入式 Lua(可以被认为是 Lua 的一种变体),它为嵌入式世界提供了 Lua 编程语言的完整实现,扩展了它的特定功能,以实现高效和可移植的软件嵌入式开发。eLua 在微控制器等较小的设备上运行,并提供常规 Lua 桌面版本的全部功能。有关 eLua 的更多信息,请访问:http ://www.eluaproject.net

28.1 预赛

当前的 SWIG 实现旨在与 Lua 5.0.x、5.1.x 和 5.2.x 一起使用。它应该适用于更高版本的 Lua,但由于大量 API 更改,肯定不适用于 Lua 4.0。可以将 Lua 模块静态链接或动态链接到解释器中(通常 Lua 静态链接其库,因为动态链接并非在所有平台上都可用)。从 eLua 0.8 开始,SWIG 还支持 eLua。由于 SWIG 2.x 和 SWIG 3.0 之间的重大变化以及测试平台的不可用,eLua 状态被降级为“实验性”。

28.2 运行 SWIG

假设您定义了一个 SWIG 模块,如下所示:

C++%module example%{#include "example.h"%}int gcd(int x, int y);extern double Foo;

要构建 Lua 模块,请使用 -lua 选项运行 SWIG 。

Bash$ swig -lua example.i

如果构建 C++ 扩展,请添加 -c++ 选项:

Bash$ swig -c++ -lua example.i

这将创建一个 C/C++ 源文件 example_wrap.c 或 example_wrap.cxx。生成的 C 源文件包含需要编译并与 C/C++ 应用程序的其余部分链接以创建扩展模块的低级包装器。

包装文件的名称源自输入文件的名称。例如,如果输入文件是 example.i,则包装文件的名称是example_wrap.c。要更改此设置,您可以使用 -o 选项。封装的模块将导出一个函数 “int luaopen_example(lua_State* L)”,必须调用该函数向 Lua 解释器注册模块。名称“luaopen_example”取决于模块的名称。

要构建 eLua 模块,请使用 -lua 运行 SWIG并添加 -elua 或 -eluac。

Bash$ swig -lua -elua example.i

要么

Bash$ swig -lua -eluac example.i

-elua 选项将所有 C 函数包装器和变量 get/set 包装器置于 rotables。它还生成一个元表,用于控制从 eLua 对这些变量的访问。它还提供了大量的模块尺寸压缩。另一方面,-eluac 选项将所有包装器放在单一 rotable 中。使用此选项,无论模块有多大,都不会消耗额外的微控制器 SRAM(粗压缩)。但是有一个问题:元表不是用-eluac生成的。要从 eLua 访问任何值,必须直接调用与该值关联的包装函数。

28.2.1 附加命令行选项

下表列出了可用于 Lua 模块的其他命令行选项。也可以使用以下命令查看它们:

Bashswig -lua -help

Lua 特定选项

-elua

为运行 elua 的小型设备生成 LTR 兼容包装器。

-eluac

elua 的“粗略压缩”模式下的 LTR 兼容包装器。

-nomoduleglobal

不要将模块名称注册为全局变量,而是从对 require 的调用返回模块表。

-no-old-metatable-bindings

禁用向后兼容性:旧式绑定名称生成和其他一些内容。解释包含在适当的后续部分中。

-squash-bases

将给定类的所有继承树中的符号压缩到自身中。模拟 SWIG3.0 之前的继承。微不足道地加快了速度,但会增加内存消耗。

28.2.2 编译链接和解释器

通常 Lua 被嵌入到另一个程序中,并且会被静态链接。下面给出了一个非常简单的独立解释器(min.c):

C++#include <stdio.h>#include "lua.h"#include "lualib.h"#include "lauxlib.h" extern int luaopen_example(lua_State* L); // declare the wrapped module int main(int argc, char* argv[]){ lua_State *L; if (argc<2) { printf("%s: <filename.lua>\n", argv[0]); return 0; } L=lua_open(); luaopen_base(L); // load basic libs (eg. print) luaopen_example(L); // load the wrapped module if (luaL_loadfile(L, argv[1])==0) // load and run the file lua_pcall(L, 0, 0, 0); else printf("unable to load %s\n", argv[1]); lua_close(L); return 0;}

在 Lua 发行版 src/lua/lua.c 中可以找到一组大大改进的代码。包含您的模块,只需添加外部声明并在相关位置添加 #define LUA_EXTRALIBS {"example",luaopen_example}。

用于编译和链接的确切命令因平台而异。这是执行此操作的一组可能的命令:

Bash$ swig -lua example.i -o example_wrap.c $ gcc -I/usr/include/lua -c min.c -o min.o $ gcc -I/usr/include/lua -c example_wrap.c -o example_wrap .o $ gcc -c example.c -o example.o $ gcc -I/usr/include/lua -L/usr/lib/lua min.o example_wrap.o example.o -o my_lua

对于 eLua,源代码必须与 SWIG 生成的包装器一起构建。确保 eLua 源文件 platform_conf.h 和 auxmods.h已使用新模块的条目进行更新。请注意:“mod”是模块名称。

C++/* 示例platform_conf.h */ #define LUA_PLATFORM_LIBS_ROM\ _ROM( AUXLIB_PIO, luaopen_pio, pio_map )\ _ROM( AUXLIB_TMR, luaopen_tmr, tmr_map )\ _ROM( AUXLIB_MOD, .... mapopen_)

C++/* 示例 auxmods.h */ #define AUXLIB_PIO "pio" LUALIB_API int ( luaopen_pio )(lua_State *L ); #define AULIB_MOD "mod" LUALIB_API int ( luaopen_mod )(lua_State *L ); ....

有关构建和配置 eLua 的更多信息,请访问: http ://www.eluaproject.net/doc/v0.8/en_building.html

28.2.3 编译动态模块

大多数但并非所有平台都支持模块的动态加载(Windows 和 Linux 支持)。请参阅 Lua 手册以确定您的平台是否支持它。为了编译动态加载的模块,可以使用相同的包装器。假设您有需要链接到名为 example.c 的文件中的代码,命令将是这样的:

Bash$ swig -lua example.i -o example_wrap.c $ gcc -fPIC -I/usr/include/lua -c example_wrap.c -o example_wrap.o $ gcc -fPIC -c example.c -o example.o $ gcc -shared -I/usr/include/lua -L/usr/lib/lua example_wrap.o example.o -o example.so

SWIG 生成的包装器可以编译并链接到 Lua 5.1.x 及更高版本。加载非常简单。

Luarequire("example")

对于使用 Lua 5.0.x 的用户,您还需要一个带有 loadlib 函数的解释器(例如使用 Lua 编译的默认解释器)。为了动态加载模块,您必须使用两个参数调用 loadlib 函数:共享库的文件名和 SWIG 导出的函数。调用 loadlib 应该返回函数,然后调用它来初始化模块

Luamy_init=loadlib("example.so", "luaopen_example") -- 对于 Unix/Linux --my_init=loadlib("example.dll", "luaopen_example") -- 对于 Windows assert(my_init) -- 确保它不是nil my_init() -- 调用 lib 的 init fn

或者可以在一行 Lua 代码中完成

Luaassert(loadlib("example.so", "luaopen_example"))()

如果代码不起作用,请不要惊慌。最好的办法是将模块和解释器复制到一个目录中,然后执行解释器并尝试手动加载模块(注意,所有这些代码都区分大小写)。

Luaa, b, c=package.loadlib("example.so", "luaopen_example") -- for Unix/Linux--a, b, c=package.loadlib("example.dll", "luaopen_example") -- for Windowsprint(a, b, c)

注意:对于 Lua 5.0:

loadlib() 函数在全局命名空间中,而不是在包中。所以它只是 loadlib()。

如果'a'是一个函数,这一切都很好,你需要做的就是调用它

Lua a()

加载您的库,它将添加一个表“example”,其中添加了所有功能。

如果它不起作用,请查看错误消息,特别是消息 'b'

The specified module could not be found。

意味着找不到模块,请检查您的模块的位置和拼写。

The specified procedure could not be found.

意味着它加载了模块,但找不到命名的函数。再次检查拼写,如果可能,检查以确保正确导出函数。

'loadlib' not installed/supported

很明显(返回并查阅 Lua 文档,了解如何为您的平台启用 loadlib)。

28.2.4 使用你的模块

假设一切顺利,您将能够:

Lua$ ./my_lua > print(example.gcd(4, 6)) 2 > print(example.Foo) 3 > example.Foo=4 > print(example.Foo) 4 >

28.3 基本 C/C++ 包装之旅

默认情况下,SWIG 尝试为您的 C/C++ 代码构建一个非常自然的 Lua 接口。本节简要介绍此包装的基本方面。

28.3.1 模块

SWIG 模块指令指定 Lua 模块的名称。如果你指定`module example',那么所有的东西都会被包装到一个包含所有函数和变量的 Lua 表 'example'中。选择模块名称时,请确保不要使用与内置 Lua 命令或标准模块名称相同的名称。

28.3.2 功能

全局函数被包装为新的 Lua 内置函数。例如,

C++%module exampleint fact(int n);

创建一个内置函数example.fact(n),它的工作原理与您认为的完全一样:

C++> print example.fact(4)24>

为避免名称冲突,SWIG 创建了一个 Lua 表,其中包含所有函数、常量、类和全局变量。可以使用以下代码。这很容易覆盖现有功能,因此必须谨慎使用。此选项被视为已弃用,将在不久的将来被删除。

Lua> for k, v in pairs(example) do _G[k]=v end> print(fact(4))24>

也可以通过赋值来重命名模块。

Lua> e=example > print(e.fact(4)) 24 > print(example.fact(4)) 24

28.3.3 全局变量

支持全局变量(链接到 C 代码),并且似乎只是 Lua 中的另一个变量。然而,实际的机制更为复杂。给定一个全局变量:

C++%module exampleextern double Foo;

SWIG 将有效地生成两个函数example.Foo_set() 和 example.Foo_get()。然后将元表添加到表 'example' 以在正确的时间调用这些函数(当您尝试设置或获取 examples.Foo 时)。因此,如果您尝试将全局变量分配给另一个变量,您将在解释器中获得一个本地副本,该副本不再链接到 C 代码。

Lua> print(example.Foo) 3 > c=example.Foo -- c 是 example.Foo 的副本,不是一回事> example.Foo=4 > print(c) 3 > c=5 -- 这不会影响原来的 example.Foo > print(example.Foo, c) 4 5

因此,不可能像函数一样将全局变量“移动”到全局命名空间中。但是,可以使用分配重命名模块,以使其更方便。

Lua> e=example > -- e 和 example 是同一个表> -- 所以 e.Foo 和 example.Foo 是同一个东西> example.Foo=4 > print(e.Foo) 4

如果一个变量被 %immutable 指令标记,那么任何设置这个变量的尝试都会导致 Lua 错误。给定一个全局变量:

C++%module example%immutable;extern double Foo;%mutable;

SWIG 将允许读取 Foo 但当进行设置尝试时,将调用错误函数。

Lua> print(e.Foo) -- reading works ok4> example.Foo=40 -- but writing does notThis variable is immutablestack traceback: [C]: ? [C]: ? stdin:1: in main chunk [C]: ?

对于那些希望 SWIG 默默忽略不可变设置的人(就像以前版本的 Lua 绑定所做的那样),添加 -DSWIGLUA_IGNORE_SET_IMMUTABLE 编译选项将删除它。

与绑定的早期版本不同,现在可以向模块添加新函数或变量,就像它是一个普通表一样。这也允许用户重命名/删除现有的函数和常量(但不是链接变量,可变或不可变)。因此,建议用户在这样做时要小心。

Lua> -- example.PI 不存在> print(example.PI) nil > example.PI=3.142 -- 添加新值> print(example.PI) 3.142

如果您为 eLua 模块使用了 -eluac 选项,则在操作全局变量时必须遵循不同的方法。(这不适用于使用 -elua 生成的包装器)

Lua> -- 仅适用于 -eluac。(num 已定义) > print(example.num_get()) 20 > example.num_set(50) -- 添加新值> print(example.num_get()) 50

通常,“variable_get()” 和 “variable_set()” 形式的函数由 SWIG 自动生成以与 -eluac 一起使用。

28.3.4 常量和枚举

因为 Lua 并没有真正的常量的概念,所以 C/C++ 常量在 Lua 中并不是真正的常量。它们实际上只是将值复制到 Lua 解释器中。因此,它们可以像任何其他值一样更改。例如给出一些常量:

C++%module example%constant int ICONST=42;#define SCONST "Hello World"enum Days{SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY};

这被“有效”转换为以下 Lua 代码:

Luaexample.ICONST=42 example.SCONST="Hello World" example.SUNDAY=0 ....

常量不能保证在 Lua 中保持不变。常量的名称可能会被意外地重新分配以引用某个其他对象。不幸的是,SWIG 没有简单的方法来生成防止这种情况的代码。你只需要小心。

如果您正在使用 eLua 并且已经使用 -elua 或 -eluac 来生成您的包装器,那么宏常量和枚举应该通过一个名为"const"的可循环访问。在 eLua 中,宏常量和枚举保证保持常量,因为它们都包含在一个可循环中。从 eLua 访问常规 C 常量就像它是常规全局变量一样,只是如果尝试修改 C 常量,就会证明值不变性的属性。

Lua> print(example.ICONST)10> print(example.const.SUNDAY)0> print(example.const.SCONST)Hello World

28.3.4.1 常量/枚举和类/结构

枚举被导出到类表中。例如,给定一些枚举:

C++%module example enum Days { SUNDAY = 0, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }; struct Test { enum { TEST1 = 10, TEST2 = 20 }; #ifdef __cplusplus // C 中没有静态成员 static const int ICONST = 12; #endif };

由于 C 和 C++ 的作用域规则不同,包装 C 和 C++ 代码的行为略有不同。封装后的C++代码从Lua代码中使用如下:

Lua> print(example.SUNDAY)0> print(example.Test.TEST1)10> print(example.Test.ICONST)12

C 结构中的枚举位于全局命名空间中,并在 Lua 中按如下方式使用

Lua> print(example.SUNDAY)0> -- See the difference here> print(example.TEST1)10

兼容性说明: SWIG-3.0.0 之前的 SWIG 版本未生成上述类表成员。C 包装器没有变化,但以下代码是包装 C++ 成员常量时访问这些常量/枚举的唯一方法:

Lua> print(example.Test_TEST1)10> print(example.Test_ICONST)12

除了新绑定之外,仍会生成旧式绑定。如果使用 -no-old-metatable-bindings 选项,则不会生成这些旧式绑定。

值得一提的是,example.Test.TEST1和 example.Test_TEST1 是不同的实体,改变一个不会改变另一个。鉴于这些是常量并且它们不应该被更改,因此避免此类问题取决于您。

28.3.5 指针

SWIG 完全支持 C/C++ 指针。此外,SWIG 处理不完整的类型信息没有问题。给定 <file.h> 接口的包装:

C++%module example FILE *fopen(const char *filename, const char *mode); int fputs(const char *, FILE *); int fclose(FILE *);

包装后,您将能够以自然的方式使用 Lua 中的函数。例如:

Lua> f=example.fopen("junk", "w") > example.fputs("Hello World", f) > example.fclose(f)

与许多脚本语言不同,Lua 长期以来一直支持内置的 C/C++ 对象指针。它们被称为“用户数据”。与使用某种编码字符串的许多其他 SWIG 版本不同,所有对象都将表示为用户数据。SWIG-Lua 绑定提供了一个特殊的函数 swig_type(),如果给定一个 userdata 对象,它将以字符串形式返回指向的对象类型(假设它是一个 SWIG 包装对象)。

Lua> print(f)userdata: 003FDA80> print(swig_type(f))FILE * -- it's a FILE*

Lua 强制其用户数据的完整性,因此几乎不可能破坏数据。但是作为指针的用户,您有责任释放它,或关闭与之相关的任何资源(就像在 C 程序中一样)。这不适用于类和结构(见下文)。最后一个注意事项:如果一个函数返回一个 NULL 指针,它不会被编码为用户数据,而是作为 Lua nil。

Lua> f=example.fopen("not there", "r") -- 这将在 C 中返回一个 NULL > print(f) nil

28.3.6 结构

如果你包装了一个 C 结构,它也会被映射到一个 Lua 用户数据。通过向用户数据添加元表,这提供了一个非常自然的界面。例如,

C++struct Point{ int x, y;};

用法如下:

Lua> p=example.new_Point()> p.x=3> p.y=5> print(p.x, p.y)3 5>

为联合和 C++ 类的数据成员提供了类似的访问。

C 结构可以使用函数 new_Point() 创建,C 结构和 C++ 类都可以使用名称 Point() 创建。

如果你在上面的例子中打印出 p 的值,你会看到这样的:

Lua> print(p)userdata: 003FA320

与上一节中的指针一样,它被保存为用户数据。但是,添加了其他功能以使其更有用。SWIG 有效地创建了一些访问器/修改器函数来获取和设置数据。这些函数将被添加到用户数据的元表中。这提供了对上面显示的成员变量的自然访问(有关完整详细信息,请参见文档末尾)。

结构的 const 成员是只读的。也可以使用 immutable 指令将数据成员强制为只读。与其他不可变项一样,设置尝试将导致错误。例如:

C++struct Foo { ... %immutable; int x; // Read-only members char *name; %mutable; ...};

管理 char* 成员以及数组成员的机制与其他语言类似。这有点麻烦,应该通过定义类型映射(稍后描述)来更好地处理。

当一个结构的成员本身就是一个结构时,它被当作一个指针来处理。例如,假设您有两个这样的结构:

C++struct Foo { int a;}; struct Bar { Foo f;};

现在,假设您像这样访问 Bar 的 f 属性:

Lua> b = Bar() > x = b.f

在这种情况下,x 是指向 b 中的 Foo 的指针。这与此 C 代码生成的值相同:

C++Bar b;Foo *x = &b->f; // Points inside b

因为指针指向结构内部,所以您可以修改内容,一切都如您所愿。例如:

Lua> b = Bar() > b.f.a = 3 -- 修改结构成员的属性> x = b.f > x.a = 3 -- 修改同一个结构

对于带有 -eluac 选项的 eLua,必须使用 SWIG 生成的特定结构函数来执行结构操作。假设您有以下结构定义:

Luastruct data { int x, y; double z;}; > --From eLua> a = example.new_data()> example.data_x_set(a, 10)> example.data_y_set(a, 20)> print(example.data_x_get(a), example.data_y_get(a))10 20

通常,SWIG 会为 C 中定义的每个结构自动生成形式为 "new_struct()"、 "struct_member_get()"、"struct_member_set()"和 "free_struct()" 的函数。(请注意:这不会申请使用 -elua 选项生成的模块)

28.3.7 C++ 类

C++ 类也由 Lua UserData 包装。例如,如果你有这个类,

C++class List {public: List(); ~List(); int search(char *item); void insert(char *item); void remove(char *item); char *get(int n); int length;};

你可以像这样在 Lua 中使用它:

Lua> l = example.List() > l:insert("Ale") > l:insert("Stout") > l:insert("Lager") > print(l:get(1)) Stout > print(l :长度) 3 >

(注意:对于调用类的方法,您使用 class:method(args),而不是class.method(args),这是一个容易犯的错误。但是对于数据属性,它是class.attribute )

类数据成员的访问方式与 C 结构相同。静态类成员给 Lua 带来了一个特殊问题,因为 Lua 不支持此类功能。因此,SWIG 会生成尝试解决其中一些问题的包装器。为了说明,假设你有一个这样的类:

C++class Spam {public: static void foo(); static int bar;};

在 Lua 中,C++ 静态成员的访问方式如下:

Lua> example.Spam.foo() -- 调用 Spam::foo() > a=example.Spam.bar -- 读取 Spam::bar > example.Spam.bar=b -- 写入 Spam::bar

(当前)无法访问实例的静态成员:

Lua> s=example.Spam() -- s 是一个 Spam 实例> s.foo() -- Spam::foo() 通过一个实例 -- 不起作用

兼容性说明:在 SWIG-3.0.0 之前的版本中,只有以下名称有效:

Lua> example.Spam_foo() -- 调用 Spam::foo() > a=example.Spam_bar -- 读取 Spam::bar > example.Spam_bar=b -- 写入 Spam::bar

现在默认生成两个样式名称。但是,如果使用 -no-old-metatable-bindings 选项,那么除了普通名称之外,不会生成向后兼容的名称。

28.3.8 C++继承

SWIG 完全了解与 C++ 继承相关的问题。因此,如果你有这样的课程

C++class Foo {...}; class Bar : public Foo {...};

如果你有这样的功能

C++void spam(Foo *f);

然后函数 spam() 接受一个 Foo 指针或一个指向从 Foo 派生的任何类的指针。

在 SWIG 中使用多重继承是安全的。

28.3.9 指针、引用、值和数组

在 C++ 中,函数可以通过多种不同的方式接收和操作对象。例如:

C++void spam1(Foo *x); // 通过指针传递void spam2(Foo &x); // 通过引用传递void spam3(Foo x); // 按值传递void spam4(Foo x[]); // 对象数组

在 SWIG 中,没有像这样的详细区分——具体来说,只有“对象”。没有指针、引用、数组等。因此,SWIG 在包装器代码中将所有这些类型统一在一起。例如,如果您确实拥有上述功能,那么这样做是完全合法的:

Lua> f = Foo() -- 创建一个 Foo > spam1(f) -- 好的。指针> spam2(f) -- 好的。引用> spam3(f) -- 好的。值。> spam4(f) -- 好的。数组(1 个元素)

返回值也会发生类似的行为。例如,如果你有这样的功能,

C++Foo *spam5(); Foo &spam6(); Foo spam7();

那么所有三个函数都将返回一个指向某个 Foo 对象的指针。由于第三个函数(spam7)返回一个值,所以使用新分配的内存来保存结果并返回一个指针(当返回值被垃圾回收时,Lua会释放这块内存)。另外两个是假定由 C 代码管理的指针,因此不会被垃圾收集。

28.3.10 C++ 重载函数

SWIG 主要支持 C++ 重载函数、方法和构造函数。例如,如果您有两个这样的函数:

C++void foo(int); void foo(char *c);

你可以直接在 Lua 中使用它们:

Lua> foo(3) -- foo(int) > foo("Hello") -- foo(char *c)

然而由于 Lua 的强制机制有时会做一些奇怪的事情。

Lua> foo("3") -- "3" 可以被强制转换为 int,所以它调用了 foo(int)!

由于这种强制机制是 Lua 的一个组成部分,除了重命名函数(见下文)之外,没有其他简单的方法可以解决这个问题。

同样,如果你有这样一个类,

C++class Foo {public: Foo(); Foo(const Foo &); ...};

你可以这样写 Lua 代码:

Lua> f = Foo() -- 创建一个 Foo > g = Foo(f) -- 复制 f

重载支持不像 C++ 那样灵活。有时,有些方法 SWIG 无法消除歧义。例如:

C++void spam(int);void spam(short);;

或者

C++void foo(Bar *b); void foo(Bar &b);

如果出现此类声明,您将收到如下警告消息:

Plain Textexample.i:12: Warning 509: Overloaded method spam(short) effectively ignored,example.i:11: Warning 509: as it is shadowed by spam(int).

要解决此问题,您需要忽略或重命名其中一种方法。例如:

C++%rename(spam_short) spam(short);...void spam(int);void spam(short); // 作为 spam_short 访问

要么

C++%ignore spam(short);...void spam(int);void spam(short); // Ignored

SWIG 使用消歧方案解决重载的函数和方法,该方案根据一组类型优先规则对声明进行排序和排序。声明在输入中出现的顺序无关紧要,除非出现歧义——在这种情况下,第一个声明优先。

有关重载的更多信息,请参阅“SWIG 和 C++”一章。

处理Lua强制机制,优先级大致是(整数、浮点数、字符串、用户数据)。但是最好重命名函数而不是依赖顺序。

28.3.11 C++ 运算符

SWIG 可以自动处理某些 C++ 重载运算符。例如,考虑这样一个类:

C++class Complex {private: double rpart, ipart;public: Complex(double r = 0, double i = 0) : rpart(r), ipart(i) { } Complex(const Complex &c) : rpart(c.rpart), ipart(c.ipart) { } Complex &operator=(const Complex &c); Complex operator+(const Complex &c) const; Complex operator-(const Complex &c) const; Complex operator*(const Complex &c) const; Complex operator-() const; double re() const { return rpart; } double im() const { return ipart; }};

包装后,它的工作原理与您期望的一样:

Lua> c = Complex(3, 4) > d = Complex(7, 8) > e = c + d > e:re() 10.0 > e:im() 12.0

运算符重载支持的一个限制是 SWIG 无法完全处理未定义为类的一部分的运算符。例如,如果你有这样的代码

C++class Complex { ... friend Complex operator+(double, const Complex &c); ... };

那么 SWIG 不知道如何处理友元函数——事实上,它只是忽略它并发出警告。您仍然可以包装运算符,但您可能必须将其封装在一个特殊的函数中。例如:

C++%rename(Complex_add_dc) operator+(double, const Complex &);...Complex operator+(double, const Complex &c);

有一些方法可以使用 %extend 指令使这个运算符作为类的一部分出现。继续阅读。

另外,请注意某些操作符不能完全映射到 Lua,一些 Lua 操作符不能完全映射到 C++ 操作符。例如,重载赋值运算符不映射到 Lua 语义,将被忽略,C++ 不支持 Lua 的连接运算符 ( .. )。

为了在 SWIG 中的不同语言中保持最大的兼容性,Lua 绑定使用与 python 相同的一组运算符名称。尽管在内部它将函数重命名为其他名称(为了使用 Lua)。

当前可以重载的运算符列表(和替代函数名称)是:

• __add__ 运算符+

• __sub__ 运算符-

• __mul__运算符 *

• __div__ 运算符/

• __unm__ 一元减

• __call__ operator ()(常用于函子类)

• __pow__ 指数 fn(没有 C++ 等价物,Lua 使用 ^)

• __concat__ 连接运算符(Lua 的..)

• __eq__ 运算符==

• __lt__ 运算符<

• __le__ 运算符<=

注意:在 Lua 中,只定义了等于、小于和小于等于运算符。其他运算符 (!=, >, >=) 是通过使用未应用于其他运算符结果的逻辑实现的。

以下操作符不能重载(主要是Lua不支持)

• ++ 和 --

• +=、-=、*= 等

• % 运算符(您必须使用 math.mod)

• 赋值运算符

• 所有按位/逻辑运算

SWIG 还接受将对象转换为字符串的 __str__() 成员函数。这个函数应该返回一个 const char*,最好是静态内存。这将用于Lua 中的 print() 和 tostring() 函数。假设 Complex 类有一个函数

C++const char* __str__(){ static char buffer[255]; sprintf(buffer, "Complex(%g, %g)", this->re(), this->im()); return buffer;}

那么这将支持Lua中的以下代码

Lua> c = Complex(3, 4)> d = Complex(7, 8)> e = c + d> print(e)Complex(10, 12)> s=tostring(e) -- s is the number in string form> print(s)Complex(10, 12)

也可以重载运算符[],但目前这不能自动执行。要重载运算符[],您需要提供两个函数, __getitem__()和__setitem__()

C++class Complex { //.... double __getitem__(int i)const; // i 是索引,返回数据 void __setitem__(int i, double d); // i 是索引,d 是数据};

C++ 操作符被映射到 Lua 预定义的元函数。类从其基础继承以下元函数列表(因此继承了以下运算符和伪运算符):

• __add__

• __sub__

• __mul__

• __div__

• __unm__

• __mod__

• __call__

• __pow__

• __concat__

• __eq__

• __lt__

• __le__

• __len__

• __getitem__

• __setitem__

• __tostring 由 Lua 内部用于 tostring() 函数。__str__ 映射到这个函数

没有其他 lua 元函数被继承。例如,__gc 不是继承的,必须在每个类中重新定义。__tostring 受到特殊处理。如果在类和类库中不存在,SWIG 将提供一个默认值。

28.3.12 带有 %extend 的类扩展

SWIG 更有趣的特性之一是它可以使用新方法扩展结构和类。在上一节中,Complex 类将从 __str__() 方法以及对运算符重载的一些修复中受益匪浅。如果需要,它还可以用于向类添加其他功能。

原始的 Complex Class

C++class Complex {private: double rpart, ipart;public: Complex(double r = 0, double i = 0) : rpart(r), ipart(i) { } Complex(const Complex &c) : rpart(c.rpart), ipart(c.ipart) { } Complex &operator=(const Complex &c); Complex operator+(const Complex &c) const; Complex operator-(const Complex &c) const; Complex operator*(const Complex &c) const; Complex operator-() const; double re() const { return rpart; } double im() const { return ipart; }};

现在我们用一些新代码扩展它

C++%extend Complex { const char *__str__() { static char tmp[1024]; sprintf(tmp, "Complex(%g, %g)", $self->re(), $self->im()); return tmp; } bool operator==(const Complex& c) { return ($self->re()==c.re() && $self->im()==c.im()); } };

现在,在 Lua

Lua> c = Complex(3, 4) > d = Complex(7, 8) > e = c + d > print(e) -- print 使用 __str__ 得到字符串形式来打印Complex(10, 12) > print( e==Complex(10, 12)) -- 测试 == 运算符true > print(e!=Complex(12, 12)) -- != 使用 == 运算符true

Extend 适用于 C 和 C++ 代码、类和结构。它不会以任何方式修改底层对象——扩展只显示在 Lua 界面中。唯一需要注意的是代码必须使用“$self”而不是“this”,并且您无法访问代码的受保护/私有成员(因为您不是该课程的正式成员)。

28.3.13 使用 %newobject 释放内存

如果你有一个像这样分配内存的函数,

C++char *foo() { char *result = (char *) malloc(...); ... return result;}

那么 SWIG 生成的包装器将发生内存泄漏——返回的数据将被复制到一个字符串对象中,而旧的内容将被忽略。

要修复内存泄漏,请使用 %newobject 指令。

C++%newobject foo; ... char *foo();

这将释放分配的内存。

28.3.14 C++ 模板

C++ 模板不会给 SWIG 带来大问题。但是,为了创建包装器,您必须告诉 SWIG 为特定模板实例化创建包装器。为此,您可以使用模板指令。例如:

C++%module example%{#include "pair.h"%} template<class T1, class T2>struct pair { typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair(); pair(const T1&, const T2&); ~pair();}; %template(pairii) pair<int, int>;

在 Lua:

Lua> p = example.pairii(3, 4) > print(p.first, p.second) 3 4

显然,模板包装比这个例子中显示的更多。更多细节可以在 SWIG 和 C++ 章节中找到。稍后会出现一些更复杂的例子。

28.3.15 C++ 智能指针

在某些 C++ 程序中,通常使用由所谓的“智能指针”包装的类。通常,这涉及使用实现operator->() 的模板类,如下所示:

C++template<class T> class SmartPtr { ... T *operator->(); ... }

那么,如果你有这样的课程,

C++class Foo {public: int x; int bar();};

智能指针将在 C++ 中使用,如下所示:

C++SmartPtr<Foo> p = CreateFoo(); // 以某种方式创建(未显示)... p->x = 3; // Foo::x int y = p->bar(); // Foo::bar

要包装它,只需将 SmartPtr 类和低级 Foo 对象告诉 SWIG。确保在必要时使用模板实例化 SmartPtr。例如:

C++%module example... %template(SmartPtrFoo) SmartPtr<Foo>; ...

现在,在 Lua 中,一切都应该“正常工作”:

Lua> p = example.CreateFoo() -- 以某种方式创建一个智能指针> px = 3 -- Foo::x > print(p:bar()) -- Foo::bar

如果您需要访问由 operator->() 本身返回的底层指针,只需使用 __deref__() 方法。例如:

Lua> f = p:__deref__() -- 返回底层 Foo *

28.3.16 C++ 异常

Lua 本身不支持异常,但它有类似的 errors。当 Lua 函数因错误而终止时,它会向调用者返回一个值。SWIG 自动映射任何被抛出 Lua 错误的基本类型。因此对于函数:

C++int message() throw(const char *) { throw("I died."); return 1;}

SWIG 会自动将其转换为 Lua 错误。

Lua> message()I died.stack traceback: [C]: in function 'message' stdin:1: in main chunk [C]: ?>

如果你想捕捉异常,你必须使用 pcall() 或 xpcall(),它们在 Lua 手册中有说明。使用 xpcall 将允许您获得额外的调试信息(例如堆栈跟踪)。

Lua> function a() b() end -- function a() calls function b()> function b() message() end -- function b() calls C++ function message(), which throws > ok, res=pcall(a) -- call the function> print(ok, res)false I died.> ok, res=xpcall(a, debug.traceback) -- call the function> print(ok, res)false I died.stack traceback: [C]: in function 'message' runme.lua:70: in function 'b' runme.lua:67: in function <runme.lua:66> [C]: in function 'xpcall' runme.lua:95: in main chunk [C]: ?

SWIG 能够毫无问题地抛出数字类型、枚举、字符、char* 和 std::string。它还为 std::exception 及其派生类编写了类型映射,将异常转换为错误字符串。

然而,抛出其他类型的对象并不是那么简单。抛出的对象在 'catch' 块之外无效。因此,它们不能退还给口译员。解决这个问题的显而易见的方法是返回对象的副本,或者将对象转换为字符串并返回。尽管执行前者似乎很明显,但在某些情况下这是不可能的,最明显的是当 SWIG 没有关于对象的信息,或者对象不可复制/创建时。

因此,默认情况下 SWIG 将所有抛出的对象转换为字符串并返回它们。所以给定一个函数:

C++void throw_A() throw(A*) { throw new A(); }

SWIG 只会将其(不好地)转换为字符串并将其用作错误。(这不是很有用,但它总是有效)。

Lua> throw_A()object exception:A *stack traceback: [C]: in function 'unknown' stdin:1: in main chunk [C]: ?>

要从 SWIG 中获得更有用的行为,您必须:提供一种将异常转换为字符串的方法,或者抛出可以复制的对象。

如果您有自己的类想要输出为字符串,则需要添加如下类型的类型映射:

C++%typemap(throws) my_except %{ lua_pushstring(L, $1.what()); // 假设 my_except::what() 返回一个 const char* 消息 SWIG_fail; // 触发错误处理程序%}

如果您希望将您的异常返回给解释器,它首先必须是可复制的。然后,您必须有一个额外的%apply 语句,以告诉 SWIG 将此对象的副本返回给解释器。例如:

C++%apply SWIGTYPE EXCEPTION_BY_VAL {Exc}; // tell SWIG to return Exc by value to interpreter class Exc {public: Exc(int c, const char *m) { code = c; strncpy(msg, m, 256); } int code; char msg[256];}; void throw_exc() throw(Exc) { throw(Exc(42, "Hosed"));}

然后可以使用以下代码(注意:我们使用 pcall 来捕获错误以便我们可以处理异常)。

Lua> ok, res=pcall(throw_exc) > print(ok) false > print(res) userdata: 0003D880 > print(res.code, res.msg) 42 Hosed >

注意:也有可能(虽然乏味)让一个函数抛出几种不同类型的异常。要处理这将需要一个 pcall,然后是一组检查错误类型的 if 语句。

所有这些代码都假定您的 C++ 代码使用异常规范(很多都没有)。如果它没有查阅“使用 %catches 进行异常处理”部分和“使用 %exception 进行异常处理”部分,以获取有关如何向函数或全局(分别)添加异常规范的更多详细信息。

28.3.17 命名空间

由于 SWIG-3.0.0 C++ 命名空间是通过 %nspace 功能支持的。

命名空间被映射到 Lua 表中。这些表中的每一个都包含在适当的命名空间中定义的名称。命名空间结构(又名嵌套命名空间)被保留。考虑以下 C++ 代码:

C++%module example%nspace MyWorld::Nested::Dweller;%nspace MyWorld::World; int module_function() { return 7; }int module_variable = 9; namespace MyWorld { class World { public: World() : world_max_count(9) {} int create_world() { return 17; } const int world_max_count; // = 9 }; namespace Nested { class Dweller { public: enum Gender { MALE = 0, FEMALE = 1 }; static int count() { return 19; } }; }}

现在,从 Lua 用法如下:

Lua> print(example.module_function())7> print(example.module_variable)9> print(example.MyWorld.World():create_world())17> print(example.MyWorld.World.world_max_count)9> print(example.MyWorld.Nested.Dweller.MALE)0> print(example.MyWorld.Nested.Dweller.count())19>

28.3.17.1 兼容性说明

如果 SWIG 以向后兼容的方式运行,即没有 -no-old-metatable-bindings 选项,则会生成其他旧式名称(注意下划线):

Lua9> print(example.MyWorld.Nested.Dweller_MALE)0> print(example.MyWorld.Nested.Dweller_count())11>

28.3.17.2 名称

如果 SWIG 在没有 -no-old-metatable-bindings 选项的情况下启动,则它进入向后兼容模式。在此模式下,它会尝试为静态函数、类静态常量和类枚举生成其他名称。这些名称采用 classname_symbolname 形式,并添加到类周围的范围中。如果启用了 %nspace,则类命名空间将作为作用域。如果没有命名空间,或者 %nspace 被禁用,则模块被视为类命名空间。

考虑以下 C++ 代码

C++%module example%nspace MyWorld::Test;namespace MyWorld {class Test { public: enum { TEST1 = 10, TEST2 } static const int ICONST = 12;};class Test2 { public: enum { TEST3 = 20, TEST4 } static const int ICONST2 = 23;}

在向后兼容模式下,除了通常的名称外,还会生成以下名称(注意下划线):

Lua9> print(example.MyWorld.Test_TEST1) -- Test has %nspace enabled10> print(example.MyWorld.Test_ICONST) -- Test has %nspace enabled12> print(example.Test2_TEST3) -- Test2 doesn't have %nspace enabled20> print(example.Test2_ICONST2) -- Test2 doesn't have %nspace enabled23>

在 C 模式下,枚举与枚举略有不同。根据 C 标准,来自 C 结构的枚举被导出到周围的作用域,没有任何前缀。假装 Test2 是一个结构,而不是类,那就是:

Lua> print(example.TEST3) -- NOT Test2_TEST320>

28.3.17.3 继承

继承的内部组织发生了变化。考虑以下 C++ 代码:

C++%module exampleclass Base { public: int base_func()};class Derived : public Base { public: int derived_func()}

让我们暂时假设类成员函数存储在 .fn 表中。以前,当在模块初始化期间将类导出到 Lua 时,对于每个派生类,所有服务表 ST(即“.fn”)都被压缩并添加到相应的派生类 ST 中:类 Base 的.fn表中的所有内容都被复制到.fn类派生表等。这是一个递归过程,所以最终派生类的整个继承树都被压缩成派生类。

这意味着在模块初始化后对 Base 类所做的任何更改都不会影响类 Derived:

Luabase = example.Base() der = example.Derived() > print(base.base_func) function: 0x1367940 > getmetatable(base)[".fn"].new_func = function (x) return x -- 添加新函数class Base(对于类,而不是实例!)> print(base.new_func) -- 检查此函数function > print(der.new_func) -- 不起作用。Derived 不再检查 Base。nil>

此行为已更改。现在,除非提供了 -squash-bases 选项,否则 Derived 会存储它的基数列表,如果在它自己的服务表中找不到某个符号,则会搜索它的基数。选项 -squash-bases 将有效地返回旧行为。

Lua> print(der.new_func) -- Now it worksfunction>

28.4 Typemap

本节解释什么是类型映射 typemap 以及如何使用它们。在大多数情况下,SWIG 的默认包装行为就足够了。然而,有时 SWIG 可能需要一些额外的帮助来了解应用哪种类型映射来提供最佳包装。本节将解释如何使用 typemaps 以达到最佳效果

28.4.1 什么是类型映射?

类型映射只不过是附加到特定 C 数据类型的代码生成规则。例如,要将整数从 Lua 转换为 C,您可以像这样定义一个类型映射:

C++%module example %typemap(in) int { $1 = (int) lua_tonumber(L, $input); printf("Received an integer : %d\n", $1);}%inline %{extern int fact(int n);%}

注意:你不应该使用这个类型映射,因为 SWIG 已经有一个用于这个任务的类型映射。这纯粹是举例。

类型映射总是与代码生成的某些特定方面相关联。在这种情况下,“in”方法是指将输入参数转换为 C/C++。数据类型 int 是将应用类型映射的数据类型。提供的 C 代码用于转换值。在这段代码中,使用了许多以 开头的特殊变量。1 变量是 int 类型局部变量的占位符。

本例编译成Lua模块后,其操作如下:

Lua> require "example"> print(example.fact(6))Received an integer : 6720

28.4.2 使用类型映射

对于所有常见类型(int、float、short、long、char*、enum 等),SWIG 内置了许多现成的书面类型映射,SWIG 会自动使用这些类型映射,您无需付出任何努力。

然而,对于使用输入/输出参数或数组的更复杂的函数,您将需要使用 <typemaps.i>,它包含这些情况下的类型映射。例如,考虑以下函数:

C++void add(int x, int y, int *result) { *result = x + y; } int sub(int *x1, int *y1) { return *x1-*y1; } void swap(int *sx, int *sy) { int t=*sx; *sx=*sy; *sy=t; }

程序员很清楚,'result'是一个输出参数,'x1'和'y1'是输入参数,'sx'和'sy'是输入/输出参数。但是对于 SWIG 来说并不明显,因此 SWIG 必须告知它们是哪种类型,以便它可以相应地进行包装。

一种方法是重命名参数名称以帮助 SWIG,例如 void add(int x, int y, int *OUTPUT),但是使用 %apply 来实现相同的结果更容易,如下所示。

C++%include <typemaps.i>%apply int* OUTPUT {int *result}; // int *result is output%apply int* INPUT {int *x1, int *y1}; // int *x1 and int *y1 are input%apply int* INOUT {int *sx, int *sy}; // int *sx and int *sy are input and output void add(int x, int y, int *result);int sub(int *x1, int *y1);void swap(int *sx, int *sy);

包装后,它会给出以下结果:

Lua> require "example"> print(example.add(1, 2))3> print(demo.sub(1, 2))-1> a, b=1, 2> c, d=demo.swap(a, b)> print(a, b, c, d)1 2 2 1

请注意,调用函数的参数中不需要“result”,因为它只是一个输出参数。对于 'sx' 和 'sy' 它们必须被传入(因为它们是输入),但原始值不会被修改(Lua 没有通过引用传递的特性)。然后将修改后的结果作为两个返回值返回。所有 INPUT/OUTPUT/INOUT 参数的行为方式类似。

注意:必须以完全相同的方式处理 C++ 引用。但是 SWIG 会自动将const int&包装为输入参数(因为它显然是输入)。

28.4.3 类型映射和数组

数组给 SWIG 带来了挑战,因为像指针一样,SWIG 不知道这些是输入值还是输出值,SWIG 也不知道数组应该有多大。然而,在适当的指导下,SWIG 可以轻松地包装数组以方便使用。

给定函数:

C++extern void sort_int(int* arr, int len); extern void sort_double(double* arr, int len);

SWIG 基本上有两种方法可以解决这个问题。第一种方法,使用 <carrays.i> 库在 C/C++ 中创建一个数组,然后可以在 Lua 中填充并传递给函数。它有效,但有点乏味。更多细节可以在 carrays.i文档中找到。

第二种更直观的方法是将 Lua 表直接传递给函数,并让 SWIG 在 Lua 表和 C 数组之间自动转换。在<typemaps.i> 文件中,有已准备好的类型映射来执行此任务。使用它们又是一个以正确方式使用 %apply 的问题。

下面的包装器文件显示了 carray 的使用以及使用 typemap 来包装数组。

C++// 使用 C 数组%include <carrays.i> // 这声明了一批用于操作 C 整数数组的函数%array_functions(int, int) extern void sort_int(int* arr, int len); // 要包装的函数 // 使用类型映射%include <typemaps.i> %apply (double *INOUT, int) {(double* arr, int len)}; extern void sort_double(double* arr, int len); // 要包装的函数

包装后,这两个函数都可以被调用,但使用起来不同:

Luarequire "example" ARRAY_SIZE=10 -- 将 C 数组传递给 sort_int() arr=example.new_int(ARRAY_SIZE) --for i=0, ARRAY_SIZE-1 do -- 索引 0..9(就像C) example.int_setitem(arr, i, math.random(1000)) end example.sort_int(arr, ARRAY_SIZE) -- 调用函数example.delete_int(arr) -- 必须删除分配的内存 -- 使用typemap使用 Lua 表调用-- 需要注意的一项:typemap 创建一个副本,而不是就地编辑t={} -- for i=1, ARRAY_SIZE do -- 索引 1..10 ( Lua style) t[i]=math.random(1000)/10 end t=example.sort_double(t) -- 用结果替换t

显然,通过编写 Lua 函数来执行从表到 C 数组的转换,第一个版本可以变得不那么乏味。在%luacode 指令是好这一点。有关此示例,请参阅 SWIG\Examples\lua\arrays。

警告:在 C 中索引从 0 开始,在 Lua 中索引从 ONE 开始。SWIG 期望 C 数组为 0..N-1 填充,Lua 表为 1..N,(索引遵循语言规范)。在类型映射中,当它将表转换为数组时,它会相应地悄悄更改索引。如果您有一个返回索引的 C 函数,请注意这种行为。

注意:SWIG 也可以以类似的方式支持指针数组。

28.4.4 类型映射和指针到指针函数

几个 C++ 库使用指针到指针函数来创建其对象。这些函数需要一个指向指针的指针,然后用指向新对象的指针填充该指针。Microsoft 的 COM 和 DirectX 以及许多其他库都有这种功能。下面给出一个例子:

Cstruct iMath; // some structureint Create_Math(iMath** pptr); // its creator (assume it mallocs)

它将与以下 C 代码一起使用:

CiMath* ptr;int ok;ok=Create_Math(&ptr);// do things with ptr//...free(ptr); // dispose of iMath

SWIG 有一个现成的写好的类型映射来处理<typemaps.i> 中的这种函数。它提供了正确的包装以及设置标志来通知 Lua 有问题的对象应该被垃圾收集。因此代码很简单:

C%include <typemaps.i>%apply SWIGTYPE** OUTPUT{iMath **pptr }; // tell SWIG it's an output struct iMath; // some structureint Create_Math(iMath** pptr); // its creator (assume it mallocs)

用法如下:

Luaok, ptr=Create_Math() -- ptr is an iMath* which is returned with the int (ok)ptr=nil -- the iMath* will be GC'ed as normal

28.5 编写类型映射

本节介绍如何使用 %typemap 指令修改 SWIG 对各种 C/C++ 数据类型的默认包装行为。这是一个高级主题,假定您熟悉 Lua C API 以及“类型映射 ”一章中的材料。

在继续之前,应该强调的是,很少需要编写类型映射,除非您想更改包装的某些方面,或者实现默认绑定无法实现的效果。

在继续之前,您应该阅读上一节关于使用 typemaps 的内容,并查看 luatypemaps.swg 和 typemaps.i 中现有的 typemaps。这些都有很好的文档记录并且相当容易阅读。在您阅读并理解这两个文件之前,您不应尝试编写自己的类型映射(它们也可能为您提供工作基础的想法)。

28.5.1 你可以写的类型映射

可以编写许多不同类型的类型映射,完整列表可以在“类型映射”一章中找到。但是,以下是最常用的。

• 在此是用于输入函数参数

• 出这是从函数的返回类型

• argout 这是一个函数参数,它实际上返回了一些东西

• typecheck 用于确定应该调用哪个重载函数(typecheck的语法与 typemap 不同,有关详细信息,请参阅 typemaps)。

28.5.2 SWIG 的 Lua-C API

本节介绍 SWIG 特定的 Lua-C API。它不包括主要的 Lua-C api,因为这是有据可查的,不值得涵盖。

C++int SWIG_ConvertPtr(lua_State* L, int index, void** ptr, swig_type_info *type, int flags);

这是用于将 Lua 用户数据转换为 void* 的标准函数。它获取 Lua 状态中给定索引处的值并将其转换为用户数据。然后它将提供必要的类型检查,确认指针与“type”中给出的类型兼容。然后最后将 '*ptr' 设置为指针。如果 flags 设置为 SWIG_POINTER_DISOWN,这将清除对象上设置的任何所有权标志。

这将返回一个可以使用宏 SWIG_IsOK() 检查的值

C++void SWIG_NewPointerObj(lua_State* L, void* ptr, swig_type_info *type, int own);

这与 SWIG_ConvertPtr 相反,因为它推送一个新的用户数据,该用户数据包装了类型为 'type' 的指针 'ptr'。参数 'own' 指定对象是否被 Lua 拥有,如果它是 1,那么 Lua 将在处理用户数据时对对象进行 GC。

C++void* SWIG_MustGetPtr(lua_State* L, int index, swig_type_info *type, int flags, int argnum, const char* func_name);

这个函数是 SWIG_ConvertPtr() 的一个版本,除了它可以工作,或者它会触发一个带有文本错误消息的 lua_error() 。此功能很少使用,将来可能会被弃用。

C++SWIG_fail

此宏在 SWIG 包装函数的上下文中调用时,将跳转到错误处理程序代码。这将调用任何清理代码(释放任何临时变量),然后触发 lua_error。

此代码的常见用途是:

C++if (!SWIG_IsOK(SWIG_ConvertPtr( .....)){ lua_pushstring(L, "发生了不好的事情"); SWIG_fail; }

C++SWIG_fail_arg(char* func_name, int argnum, char* type)

此宏在 SWIG 包装函数的上下文中调用时,将显示错误消息并跳转到错误处理程序代码。错误消息的形式“ func_name(arg argnum ) 中的错误,预期'类型'得到'无论类型是'”

C++SWIG_fail_ptr(const char* fn_name, int argnum, swig_type_info* type);

与 SWIG_fail_arg 类似,不同之处在于它会显示 swig_type_info 信息。

28.6 自定义绑定

本节介绍向模块添加一些小的额外位以添加最后的收尾工作。

28.6.1 编写自己的自定义包装器

有时,可能需要添加您自己的特殊函数,绕过普通 SWIG 包装器方法,只使用原生 Lua API 调用。这些“本机”函数允许将您自己的代码直接添加到模块中。这是使用 %native 指令执行的,如下所示:

C++%native(my_func) int native_function(lua_State*L); // 使用 SWIG 注册 native_function() ... %{ int native_function(lua_State*L) // 我的本机代码{ ... } %}

上面例子中的 %native 指令告诉 SWIG 有一个函数 int native_function(lua_State*L); 它将添加到名为“ my_func ”的模块中。除了将其添加到函数表中之外,SWIG 不会为此函数添加任何包装器。您如何编写代码完全取决于您。

28.6.2 添加额外的 Lua 代码

除了添加额外的 C/C++ 代码外,还可以将您自己的 Lua 代码添加到模块中。一旦调用了所有其他初始化,包括 %init 代码,就会执行此代码。

指令 %luacode 将代码添加到加载时执行的模块中。通常您会使用它来将您自己的功能添加到模块中。尽管您可以轻松执行其他任务。

C++%module example; %luacode { function example.greet() print "hello world" end print "Module loaded ok"}...%}

请注意,代码不是模块表的一部分。因此,对模块的任何引用都必须添加模块名称。

如果 Lua 代码中有错误,这不会停止加载模块。SWIG 的默认行为是将错误消息打印到 stderr,然后继续。如果代码失败,可以使用 #define SWIG_DOSTRING_FAIL(STR) 定义不同的行为来更改此行为。

此功能的良好用途是添加新代码,或编写辅助函数来简化某些代码。有关此代码的示例,请参见示例/lua/arrays。

28.7 Lua绑定细节

在上一节中,介绍了 Lua 包装的高级视图。显然,很多事情发生在幕后才能实现。本节将解释有关如何实现的一些低级细节。

如果您只想使用 SWIG 并且不关心它是如何工作的,那么请停止阅读这里。这将进入代码的核心及其工作原理。它主要适用于需要了解代码中发生了什么的人。

28.7.1 将全局数据绑定到模块中。

假设您有一些想要在 C 和 Lua 之间共享的全局数据。SWIG 如何做到这一点?

C++%module example;extern double Foo;

SWIG 将有效地生成这对函数

C++void Foo_set(double);double Foo_get();

在初始化时,它会向解释器添加一个名为“example”的表,它代表模块。然后它将其所有功能添加到模块中。(注意:旧版本的 SWIG 实际上添加了 Foo_set() 和 Foo_get() 函数,当前实现不再添加这些函数。)但它还向该表添加了一个元表,它有两个函数(__index 和 __newindex )作为以及两个表(.get 和 .set)下面的 Lua 代码将展示这些隐藏的功能。

Lua> print(example)table: 003F8F90> m=getmetatable(example)> table.foreach(m, print).set table: 003F9088.get table: 003F9038__index function: 003F8FE0__newindex function: 003F8FF8> g=m['.get']> table.foreach(g, print)Foo function: 003FAFD8>

.get 和 .set 表是将变量名称 'Foo' 连接到访问器/修改器函数(Foo_set、Foo_get)的查找

__index和 __newindex代码的 Lua 等价物看起来有点像这样

Luafunction __index(mod, name) local g=getmetatable(mod)['.get'] -- gets the table if not g then return nil end local f=g[name] -- looks for the function -- calls it & returns the value if type(f)=="function" then return f() end return nilend function __newindex(mod, name, value) local s=getmetatable(mod)['.set'] -- gets the table if not s then return end local f=s[name] -- looks for the function -- calls it to set the value if type(f)=="function" then f(value) else rawset(mod, name, value) endend

这样,当您调用 ' a=example.Foo ' 时,解释器查看表 'example' 发现没有字段 'Foo' 并调用 __index。这将依次检查 '.get' 表并找到 'Foo' 的存在,然后返回 C 函数调用 'Foo_get()' 的值。同样,对于代码“ example.Foo=10 ”,解释器将检查表,然后调用 __newindex,后者将检查“.set”表并调用 C 函数“Foo_set(10)”。

28.7.2 用户数据和元表

如前所述,类和结构都作为指针保存,使用 Lua 的“userdata”结构。这个结构实际上是一个指向 C 结构 'swig_lua_userdata' 的指针,它包含指向数据的指针、指向 swig_type_info(内部 SWIG 结构)的指针和一个标志,当解释器没有不再需要它。对象的实际访问是通过附加到此用户数据的元表完成的。

元表是 Lua 5.0 的特性(这也是 SWIG 不能包装 Lua 4.0 的原因)。它是一个包含函数、运算符和属性列表的表。这就是让用户数据感觉它是一个真实的对象而不仅仅是一块内存的原因。

给定一个班级

C++%module excpp; class Point{public: int x, y; Point(){x=y=0;} ~Point(){} virtual void Print(){printf("Point @%p (%d, %d)\n", this, x, y);}};

SWIG 将创建一个模块 excpp,其中包含所有各种功能。然而,为了直观地使用用户数据,SWIG 还创建了一组元表。正如上面关于全局变量的部分所见,元表的使用允许直观地使用包装器。为了省力,代码为每个类创建一个元表并将其存储在 Lua 的注册表中。然后当一个新对象被实例化时,在注册表中找到元表和与元表关联的用户数据。目前,派生类制作基类表的完整副本,然后添加自己的附加功能。

通过查看类的元表可以看到一些内部结构:

Lua> p=excpp.Point()> print(p)userdata: 003FDB28> m=getmetatable(p)> table.foreach(m, print).type Point__gc function: 003FB6C8__newindex function: 003FB6B0__index function: 003FB698.get table: 003FB4D8.set table: 003FB500.fn table: 003FB528

'.type' 属性是类的名称。'.get' 和 '.set' 表的工作方式与模块类似,主要区别在于 '.fn' 表,它也包含所有成员函数。('__gc' 函数是类的析构函数)

启用函数的代码的 Lua 等价物看起来有点像这样

Luafunction __index(obj, name) local m=getmetatable(obj) -- gets the metatable if not m then return nil end local g=m['.get'] -- gets the attribute table if not g then return nil end local f=g[name] -- looks for the get_attribute function -- calls it & returns the value if type(f)=="function" then return f() end -- ok, so it not an attribute, maybe it's a function local fn=m['.fn'] -- gets the function table if not fn then return nil end local f=fn[name] -- looks for the function -- if found the fn then return the function -- so the interpreter can call it if type(f)=="function" then return f end return nilend

因此,当调用 'p:Print()' 时,__index 在对象元表上查找 'Print' 属性,然后查找 'Print' 函数。当它找到函数时,它返回函数,然后解释器可以调用'Point_Print(p)'

理论上,您可以使用此用户表并添加新功能,但请记住,它是一个类的所有实例之间的共享表,您很容易破坏所有实例中的功能。

注意:不透明结构(如 FILE*)和普通包装类/结构都使用相同的“swig_lua_userdata”结构。尽管不透明结构没有附加元表,或者在解释器完成它们后如何处理它们的任何信息。

注意:运算符重载基本上以相同的方式完成,通过向类的元表添加诸如“__add”和“__call”之类的函数。当前的实现有点粗糙,因为它会将任何以 '__' 开头的成员函数也添加到元表中,假设它是运算符重载。

28.7.3 内存管理

Lua 对内存管理很有帮助。'swig_lua_userdata'完全由解释器本身管理。这意味着 C 代码和 Lua 代码都不能破坏它。一旦一条 userdata 没有引用它,它不会立即收集,而是在 Lua 认为需要时收集。(你可以通过调用 Lua 函数collectgarbage()强制收集)。一旦用户数据即将被释放,解释器将检查用户数据中的元表和函数“__gc”。如果存在,则调用此方法。对于所有完整类型(即普通包装类和结构),这应该存在。'__gc' 函数将检查 'swig_lua_userdata' 以检查 'own' 字段,如果这是真的(这将用于所有拥有的数据),它将调用指针上的析构函数。

目前不建议编辑此字段或添加一些用户代码来更改行为。虽然对于那些想尝试的人来说,这里是寻找的地方。

目前也无法更改数据的所有权标志(与大多数其他脚本语言不同,Lua 不允许从解释器内部访问数据)。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 韩大 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档