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

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

作者头像
韩伟
发布2021-09-03 15:45:57
3.5K0
发布2021-09-03 15:45:57
举报

11 Typemaps

11.1 简介

很有可能,您正在阅读本章是出于以下两个原因之一;您要么想自定义 SWIG 的行为,要么无意中听到有人嘟囔着一些关于“typemaps”的难以理解的胡言乱语,然后问自己“typemaps,那些是什么?” 也就是说,让我们先做一个简短的免责声明,即“Typemaps”是一种高级自定义功能,可以直接访问 SWIG 的低级代码生成器。不仅如此,它们还是 SWIG C++ 类型系统(它自己的一个重要主题)的组成部分。typemaps 通常不是使用 SWIG 的必需部分。因此,如果您已经找到了进入本章的方法,并且对 SWIG 默认情况下已经做了什么只有一个模糊的概念,那么您可能需要重新阅读前面的章节。

11.1.1 类型转换

包装器代码生成中最重要的问题之一是编程语言之间数据类型的转换或编组。具体来说,对于每个 C/C++ 声明,SWIG 必须以某种方式生成包装器代码,允许值在语言之间来回传递。由于每种编程语言都不同地表示数据,这不是简单地将代码与 C 链接器链接在一起的问题。相反,SWIG 必须了解数据在每种语言中的表示方式以及如何操作。

为了说明这一点,假设您有一个简单的 C 函数,如下所示:

C++int factorial(int n);

要从 Python 访问此函数,需要使用一对 Python API 函数来转换整数值。例如:

C++long PyInt_AsLong(PyObject *obj); /* Python --> C */ PyObject *PyInt_FromLong(long x); /* C --> Python */

第一个函数用于将输入参数从 Python 整数对象转换为 C long。第二个函数用于将值从 C 转换回 Python 整数对象。

在包装函数内部,您可能会看到这些函数的使用方式如下:

C++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++IV SvIV(SV *sv); /* Perl --> C */ void sv_setiv(SV *sv, IV val); /* C --> Perl */

在 Tcl 中:

C++int Tcl_GetLongFromObj(Tcl_Interp *interp, Tcl_Obj *obj, long *value); Tcl_Obj *Tcl_NewIntObj(long value);

精确的细节并不是那么重要。重要的是,所有的底层类型转换都是由实用函数的集合和像这样的一小段 C 代码来处理的——你只需要阅读你喜欢的语言的扩展文档就知道它是如何工作的(剩下的练习给读者)。

11.1.2 Typemaps

由于类型处理对于包装器代码生成非常重要,因此 SWIG 允许用户完全定义(或重新定义)它。为此,使用了一个特殊的 %typemap 指令。例如:

C++/* 从 Python 转换 --> C */ %typemap(in) int { $1 = PyInt_AsLong($input); } /* 从 C 转换 --> Python */ %typemap(out) int { $result = PyInt_FromLong($1); }

乍一看,这段代码看起来有点混乱。然而,它真的没有多少。第一个类型映射(“in”类型映射)用于将值从目标语言转换为 C。第二个类型映射(“out”类型映射)用于向另一个方向转换。每个类型映射的内容都是一小段代码,直接插入到 SWIG 生成的包装器函数中。代码通常是 C 或 C++ 代码,它们将生成到 C/C++ 包装函数中。请注意,情况并非总是如此,因为某些目标语言模块允许在生成到目标语言特定文件中的类型映射中使用目标语言代码。在此代码中,扩展了许多以 为前缀的特殊变量。这些实际上只是在创建包装函数的过程中生成的 C/C++ 变量的占位符。在这种情况下,input 指的是需要转换为 C/C++ 的输入对象, result 指的是将由包装函数返回的对象。1 指的是一个 C/C++ 变量,它的类型与 typemap 声明中指定的类型相同(本例中为 int)。

一个简短的例子可能会使这更清楚一点。如果您要包装这样的函数:

C++int gcd(int x, int y);

一个包装函数大概是这样的:

C++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 背后的全部想法——它们只是让您将任意代码插入到生成的包装函数的不同部分中。因为可以插入任意代码,所以可以完全改变值的转换方式。

11.1.3 模式匹配

顾名思义,类型映射的目的是将 C 数据类型“映射”到目标语言中的类型。一旦为 C 数据类型定义了类型映射,它就会应用于输入文件中该类型的所有未来出现。例如:

C++/* 从 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 数据类型的匹配不仅仅是简单的文本匹配。事实上,类型映射完全内置于底层类型系统中。因此,类型映射不受 typedef、命名空间和其他可能隐藏底层类型的声明的影响。例如,你可以有这样的代码:

C++/* 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);

在这种情况下,即使类型名并不总是与文本 “int” 匹配,类型映射仍然适用于正确的参数。这种跟踪类型的能力是 SWIG 的关键部分——事实上,所有目标语言模块的工作只是为基本类型定义一组类型映射。然而,从来没有必要为 typedef 引入的类型名编写新的类型映射。

除了跟踪类型名称之外,类型映射也可以专门用于匹配特定的参数名称。例如,您可以编写这样的类型映射:

C++%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);...

对于某些任务,例如输入参数转换,可以为连续参数序列定义类型映射。例如:

C++%typemap(in) ( char *str, int len ) { $1 = PyString_AsString($input); /* 字符 *str */ $2 = PyString_Size($input); /* int len */ } ... int count( char *str, int len , char c);

在这种情况下,单个输入对象被扩展为一对 C 参数。此示例还提供了对涉及1、2等的不寻常变量命名方案的提示。

11.1.4 重用类型映射

类型映射通常是为特定类型和参数名称模式定义的。但是,也可以复制和重用类型映射。一种方法是使用这样的赋值:

C++%typemap(in) Integer = int; %typemap(in) (char *buffer, int size) = (char *str, int len);

在 %apply 指令中可以找到更通用的复制形式,如下所示:

C++%typemap(in) int { /* 转换整数参数 */ ... } %typemap(out) int { /* 返回一个整数值 */ ... } /* 将所有整数类型映射应用于 size_t */ %apply int { size_t };

%apply 仅获取为一种类型定义的所有类型映射并将它们应用于其他类型。注意:您可以在 %apply 的 { ... } 部分包含一组逗号分隔的类型。

应该注意的是,没有必要为 typedef 相关的类型复制类型映射。例如,如果你有这个,

C++typedef int size_t;

那么 SWIG 已经知道int 类型映射适用。你不必做任何事情。

11.1.5 typemaps 可以做什么?

Typemaps 的主要用途是在单个 C/C++ 数据类型级别定义包装器生成行为。目前,typemaps 可以解决六大类问题:

参数处理

C++int foo( int x, double y, char *s );

• 输入参数转换(“in”类型映射)。

• 重载方法中使用的类型的输入参数类型检查(“typecheck”类型映射)。

• 输出参数处理(“argout”类型映射)。

• 输入参数值检查(“检查”类型映射)。

• 输入参数初始化(“arginit”类型映射)。

• 默认参数(“默认”类型映射)。

• 输入参数资源管理(“freearg”类型映射)。

返回值处理

C++int foo(int x, double y, char *s);

• 函数返回值转换(“out”类型映射)。

• 返回值资源管理(“ret”类型映射)。

• 新分配对象的资源管理(“newfree”类型映射)。

异常处理

C++int foo(int x, double y, char *s) throw( MemoryError, IndexError );

• 处理 C++ 异常规范。(“抛出”类型图)。

全局变量

C++int foo;

• 全局变量的赋值。(“varin”类型映射)。

• 读取全局变量。(“varout”类型映射)。

成员变量

C++struct Foo { int x[20] ; };

• 将数据分配给类/结构成员。(“memberin”类型映射)。

常量创建

C++#define FOO 3 %constant int BAR = 42; Enum { ALE, LAGER, STOUT };

• 创建常量值。(“consttab”或“constcode”类型映射)。

稍后将介绍这些类型图的详细信息。此外,某些语言模块可能会定义在此列表上扩展的其他类型映射。例如,Java 模块定义了各种类型映射来控制 Java 绑定的其他方面。有关更多详细信息,请参阅特定于语言的文档。

11.1.6 typemaps 不能做什么?

类型映射不能用于定义适用于整个 C/C++ 声明的属性。例如,假设你有一个这样的声明,

C++Foo *make_Foo(int n);

并且您想告诉 SWIG make_Foo(int n)返回了一个新分配的对象(为了提供更好的内存管理)。显然,这个属性 make_Foo(INT N) 是会与属性 Foo*本身的数据类型关联。因此,为此目的使用了完全不同的 SWIG 自定义机制 ( %feature )。有关更多信息,请参阅自定义功能一章。

类型映射也不能用于重新排列或转换参数的顺序。例如,如果你有一个这样的函数:

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

你不能使用类型映射来交换参数,允许你像这样调用函数:

Pythonfoo("hello", 3) # 反转参数

如果要更改函数的调用约定,请编写辅助函数。例如:

C++%rename(foo) wrap_foo; %inline %{ void wrap_foo(char *s, int x) { foo(x, s); } %}

11.1.7 与面向切面编程的相似之处

SWIG 与面向切面的软件开发 (AOP)相似。与 SWIG 类型映射相关的 AOP 术语可以查看如下:

横切关注点:横切关注点是类型映射实现的功能的模块化,主要是从/到目标语言和 C/C++ 的类型编组。

建议:typemap 主体包含在需要编组时执行的代码。

切入点:切入点是类型映射代码生成到的包装器代码中的位置。

Aspect:Aspects 是切入点和通知的组合,因此每个 typemap 都是一个切面。

SWIG 也可以被视为具有基于%feature的第二组切面。诸如 %exception 之类的功能也是横切关注点,因为它们封装了可用于向任何函数添加日志记录或异常处理的代码。

11.1.8 本章的其余部分

本章的其余部分为想要编写新类型映射的人提供了详细信息。此信息对于打算编写新 SWIG 目标语言模块的任何人都特别重要。高级用户还可以使用此信息来编写特定于应用程序的类型转换规则。

由于类型映射与底层 C++ 类型系统紧密相关,因此后续部分假设您相当熟悉值、指针、引用、数组、类型限定符(例如,const)、结构、命名空间、模板和内存管理的基本细节在 C/C++ 中。如果没有,建议您在继续之前查阅 Kernighan 和 Ritchie 的“C 编程语言”或 Stroustrup 的“C++ 编程语言”的副本。

11.2 Typemap 规范

本节描述 %typemap 指令本身的行为。

11.2.1 定义 typemap

使用 %typemap 声明定义新类型映射。该声明的一般形式如下([ ... ] 中的部分是可选的):

%typemap( method [, modifiers ]) typelist code

method 只是一个名称,用于指定正在定义的类型映射类型。它通常是一个名称,如"in"、 "out"或"argout"。这些方法的目的将在后面描述。

modifiers 是一个可选的逗号分隔的 name="value"值列表。这些有时用于将额外信息附加到类型映射,并且通常依赖于目标语言。它们也称为类型映射属性。

typelist 是 typemap 将匹配的 C++ 类型模式的列表。该列表的一般形式如下:

Plain Texttypelist : typepattern [, typepattern, typepattern, ... ] ; typepattern : type [ (parms) ] | type name [ (parms) ] | ( typelist ) [ (parms) ]

每个类型模式要么是一个简单类型,一个简单类型和参数名称,要么是多参数类型映射的类型列表。此外,每个类型模式都可以使用临时变量(参数)列表进行参数化。稍后将解释这些变量的用途。

code 指定类型映射中使用的代码。通常这是 C/C++ 代码,但在静态类型的目标语言中,例如 Java 和 C#,这可以包含某些类型映射的目标语言代码。它可以采用以下任何一种形式:

Plain Textcode : { ... } | " ... " | %{ ... %}

请注意,预处理器将在 {} 分隔符内展开代码,但不会在最后两种分隔符样式中展开,请参阅 Preprocessor 和 Typemaps。以下是一些有效类型映射规范的示例:

C++/* 简单的类型映射声明 */ %typemap(in) int { $1 = PyInt_AsLong($input); } %typemap(in) int "$1 = PyInt_AsLong($input);"; %typemap(in) int %{ $1 = PyInt_AsLong($input); %} /* 带有额外参数名称的类型映射 */ %typemap(in) int nonnegative { ... } /* 一个类型映射中有多种类型 */ %typemap(in) int, short, long { $1 = SvIV($input) ; } /* 带有修饰符的类型映射 */ %typemap(in, doc="integer") int "$1 = scm_to_int($input);"; /* 应用于多个参数模式的类型映射 */ %typemap(in) (char *str, int len), (char *buffer, int size) { $1 = PyString_AsString($input); $2 = PyString_Size($input); } /* 带有额外模式参数的类型映射 */ %typemap(in, numinputs=0) int *output (int temp), long *output (long temp) { $1 = &temp; }

诚然,乍一看,这并不是最易读的语法。但是,各个部分的目的将变得清晰。

11.2.2 类型图作用域

定义后,类型映射对随后的所有声明保持有效。可以为输入文件的不同部分重新定义类型映射。例如:

C++// typemap1 %typemap(in) int { ... } int fact(int); // typemap1 int gcd(int x, int y); // typemap1 // typemap2 %typemap(in) int { ... } int isprime(int); // 类型映射2

typemap 范围规则的一个例外与 %extend 声明有关。%extend 用于将新声明附加到类或结构定义。因此,%extend 块中的所有声明都受在定义类本身时生效的类型映射规则的约束。例如:

C++class Foo { ... }; %typemap(in) int { ... } %extend Foo { int blah(int x); // typemap 没有效果。声明附加在 Foo 上, // 出现在 %typemap 声明之前。};

11.2.3 复制 typemap

类型映射是通过使用赋值来复制的。例如:

C++%typemap(in) Integer = int;

或这个:

C++%typemap(in) Integer, Number, int32_t = int;

类型通常由不同类型映射的集合管理。例如:

C++%typemap(in) int { ... } %typemap(out) int { ... } %typemap(varin) int { ... } %typemap(varout) int { ... }

要将所有这些类型映射复制到新类型,请使用 %apply。例如:

C++%apply int { Integer }; // 将所有 int 类型映射复制到 Integer %apply int { Integer, Number }; // 将所有 int 类型映射复制到 Integer 和 Number

%apply 的模式遵循与 %typemap 相同的规则。例如:

C++%apply int *output { Integer *output }; // 名称为%apply (char *buf, int len) { (char *buffer, int size) }; // 多个参数

11.2.4 删除 typemap

只需不定义代码就可以删除类型映射。例如:

C++%typemap(in) int; // 清除 int 的类型映射%typemap(in) 的 typemap int, long, short; // 清除 int、long、short 的类型映射%typemap(in) int *output;

在%clear 指令清除给定类型的所有typemaps。例如:

C++%clear int;// 删除所有类型的 int %clear int *output, long *output;

注意:由于 SWIG 的默认行为是由类型映射定义的,因此清除像 int 这样的基本类型将使该类型无法使用,除非您还在清除操作后立即定义一组新的类型映射。

11.2.5 typemap 的放置

类型映射声明可以在全局范围、C++ 命名空间和 C++ 类中声明。例如:

C++%typemap(in) int { ... } namespace std { class string; %typemap(in) string { ... } } class Bar { public: typedef const int & const_reference; %typemap(out) const_reference { ... } };

当 typemap 出现在命名空间或类中时,它会一直有效,直到 SWIG 输入结束(就像以前一样)。但是,类型映射考虑了本地范围。因此,这段代码

C++namespace std { class string; %typemap(in) string { ... } }

实际上是为 std::string 类型定义了一个类型映射。你可以有这样的代码:

C++namespace std { class string; %typemap(in) string { /* std::string */ ... } } namespace Foo { class string; %typemap(in) string { /* Foo::string */ ... } }

在这种情况下,有两个完全不同的类型映射适用于两种完全不同的类型(std::string和 Foo::string)。

应该注意的是,要使作用域生效,SWIG 必须知道 string 是在特定命名空间中定义的类型名。在此示例中,这是使用前向类声明类字符串完成的。

11.3 模式匹配规则

本节描述了 C/C++ 数据类型与 typemap 关联的模式匹配规则。在实践中可以通过使用还描述的调试选项来观察匹配规则。

11.3.1 基本匹配规则

类型映射使用类型和名称(通常是参数的名称)进行匹配。对于给定的 TYPE NAME 对,依次应用以下规则来查找匹配项。使用找到的第一个类型映射。

• 与 TYPE 和 NAME 完全匹配的 typemap。

• 仅与 TYPE 完全匹配的 typemap。

• 如果 TYPE 是类型为 T< TPARMS > 的 C++ 模板,其中 TPARMS 是模板参数,则该类型将被去除模板参数,然后进行以下检查:

○与 T 和 NAME 完全匹配的 typemap。

○仅与 T 完全匹配的 typemap。

如果 TYPE 包含限定符(const、volatile 等),则每个限定符一次剥离一个以形成新的剥离类型,并在剥离类型上重复上述匹配规则。最左边的限定符首先被剥离,导致最右边(或顶级)的限定符最后被剥离。例如 int const* const 首先被剥离为 int *const 然后是 int *。

如果 TYPE 是一个数组。进行了以下转换:

• 将所有维度替换为 [ANY] 并查找通用数组类型映射。

为了说明这一点,假设您有一个这样的函数:

C++int foo(const char *s);

要查找参数 const char *s 的类型映射,SWIG 将搜索以下类型映射:

Plain Textconst char *s 精确类型和名称匹配const char * 精确类型匹配char *s 类型和名称匹配(去除限定符)char * 类型匹配(去除限定符)

当可能定义多个类型映射规则时,实际上只使用找到的第一个匹配项。这是一个示例,显示了如何应用一些基本规则:

C++%typemap(in) int *x { ... typemap 1 } %typemap(in) int * { ... typemap 2 } %typemap(in) const int *z { ... typemap 3 } %typemap(in) int [4] { ... typemap 4 } %typemap(in) int [ANY] { ... typemap 5 } void A(int *x); // int *x rule (typemap 1)void B(int *y); // int * rule (typemap 2)void C(const int *x); // int *x rule (typemap 1)void D(const int *z); // const int *z rule (typemap 3)void E(int x[4]); // int [4] rule (typemap 4)void F(int x[1000]); // int [ANY] rule (typemap 5)

兼容性说明: SWIG-2.0.0 引入了一次剥离限定符的功能。以前的版本一步剥离了所有限定符。

11.3.2 Typedef 缩减匹配

如果使用上一节中的规则未找到匹配项,SWIG 会对该类型应用 typedef 缩减,并为缩减的类型重复 typemap 搜索。为了说明,假设您有这样的代码:

C++%typemap(in) int { ... typemap 1} typedef int Integer;void blah(Integer x);

要查找 Integer x 的类型映射,SWIG 将首先搜索以下类型映射:

Plain TextInteger xInteger

找不到匹配项,然后将归约Integer -> int 应用于类型并重复搜索。

Plain Textint x int --> 匹配:typemap 1

尽管两种类型可能通过 typedef 相同,但 SWIG 允许为每个类型名独立定义类型映射。这允许仅基于类型名本身进行有趣的自定义可能性。例如,您可以编写如下代码:

C++typedef double pdouble; // Positive double // typemap 1%typemap(in) double { ... get a double ...}// typemap 2%typemap(in) pdouble { ... get a positive double ...}double sin(double x); // typemap 1pdouble sqrt(pdouble x); // typemap 2

减少类型时,一次只应用一个 typedef 减少。搜索过程继续应用缩减,直到找到匹配或直到无法再进行缩减。

对于复杂的类型,减少过程可以生成一长串模式。考虑以下:

C++typedef int Integer;typedef Integer Row4[4]; void foo(Row4 rows[10]);

为了找到Row4 rows[10]参数的匹配项,SWIG 将检查以下模式,仅在找到匹配项时停止:

Plain TextRow4 rows[10] Row4 [10] Row4 rows[ANY] Row4 [ANY] # Reduce Row4 --> Integer[4] Integer rows[10][4] Integer [10][4] Integer rows[ANY][ANY] ] Integer [ANY][ANY] # Reduce Integer --> int int rows[10][4] int [10][4] int rows[ANY][ANY] int [ANY][ANY]

对于像模板这样的参数化类型,情况更加复杂。假设你有一些这样的声明:

C++typedef int Integer;typedef foo<Integer, Integer> fooii;void blah(fooii *x);

在这种情况下,将在以下类型映射模式中搜索参数fooii *x:

C++fooii *xfooii * # Reduce fooii --> foo<Integer, Integer>foo<Integer, Integer> *xfoo<Integer, Integer> * # Reduce Integer -> intfoo<int, Integer> *xfoo<int, Integer> * # Reduce Integer -> intfoo<int, int> *xfoo<int, int> *

Typemap 缩减总是应用于出现的最左边的类型。只有当不能对最左边的类型进行缩减时,才会对该类型的其他部分进行缩减。这种行为意味着您可以为 foo<int, Integer> 定义类型映射,但永远不会匹配foo<Integer, int> 的类型映射。诚然,这是相当深奥的——几乎没有实际的理由来编写这样的类型映射。当然,您可以依靠这一点来进一步混淆您的同事。

作为澄清的一点,值得强调的是 typedef 匹配只是一个 typedef 缩减过程,也就是说,SWIG 不会搜索每个可能的 typedef。给定声明中的类型,它只会减少类型,而不会构建它以寻找 typedef。例如,给定类型Struct,下面的类型映射将不会用于aStruct参数,因为 Struct已完全减少:

C++struct Struct {...};typedef Struct StructTypedef; %typemap(in) StructTypedef { ...} void go(Struct aStruct);

11.3.3 默认的 typemap 匹配规则

如果基本模式匹配规则导致没有匹配,即使在 typedef 减少之后,默认的 typemap 匹配规则依然用于寻找合适的 typemap 匹配。这些规则匹配基于保留的 SWIGTYPE 基类型的通用类型映射。例如,指针将使用 SWIGTYPE * 而引用将使用 SWIGTYPE &。更准确地说,这些规则基于 C++ 编译器在寻找合适的部分模板特化时使用的 C++ 类模板部分特化匹配规则。这意味着匹配是从可用的最专业的通用类型映射类型集中选择的。例如,当寻找与 int const * 的匹配时,规则将更喜欢匹配 SWIGTYPE const * 如果之前匹配可用 SWIGTYPE *,匹配前 SWIGTYPE。

大多数 SWIG 语言模块使用类型映射来定义 C 基元类型的默认行为。这完全是直截了当的。例如,一组按值或常量引用编组的原语类型映射是这样写的:

C++%typemap(in) int "... convert to int ...";%typemap(in) short "... convert to short ...";%typemap(in) float "... convert to float ...";...%typemap(in) const int & "... convert ...";%typemap(in) const short & "... convert ...";%typemap(in) const float & "... convert ...";...

由于类型映射匹配遵循所有 typedef 声明,因此通过 typedef 映射到原始类型的任何类型的类型或常量引用都将被这些原始类型映射中的一个选择。大多数语言模块还为 char 指针和 char 数组定义了 typemaps 来处理字符串,因此这些非默认类型也将优先使用,因为基本 typemap 匹配规则提供比默认 typemap 匹配规则更好的匹配。

下面是语言模块提供的典型默认类型的列表,显示了 “in” 类型映射的样子:

C++%typemap(in) SWIGTYPE & { ... 默认引用处理 ... }; %typemap(in) SWIGTYPE * { ... 默认指针处理 ... }; %typemap(in) SWIGTYPE *const { ... 默认指针常量处理 ... }; %typemap(in) SWIGTYPE *const& { ... 默认指针常量引用处理 ... }; %typemap(in) SWIGTYPE[ANY] { ... 一维固定大小的数组处理 ... }; %typemap(in) SWIGTYPE [] { ... 未知大小的数组处理 ... }; %typemap(in) enum SWIGTYPE { ... 枚举值的默认处理 ... };%typemap(in) const enum SWIGTYPE & { ... 常量枚举引用值的默认处理 ... }; %typemap(in) SWIGTYPE (CLASS::*) { ... 默认指针成员处理 ... }; %typemap(in) SWIGTYPE { ... 简单的默认处理 ... };

如果您想更改 SWIG 对简单指针的默认处理方式,只需重新定义 SWIGTYPE * 的规则即可。请注意,简单的默认类型映射规则用于匹配不匹配任何其他规则的简单类型:

C++%typemap(in) SWIGTYPE { ... 简单的默认处理 ... }

此 typemap 很重要,因为它是使用按值调用或返回时触发的规则。例如,如果您有这样的声明:

C++double dot_product(Vector a, Vector b);

该 Vector 类型通常会一下就对匹配 SWIGTYPE。SWIGTYPE的默认实现是将值转换为指针(如前一节所述)。

通过重新定义SWIGTYPE,可以实现其他行为。例如,如果您清除了 SWIGTYPE 的所有类型映射,则 SWIG 根本不会包装任何未知数据类型(这可能对调试有用)。或者,您可以修改 SWIGTYPE 以将对象编组为字符串,而不是将它们转换为指针。

让我们考虑一个示例,其中定义了以下类型映射,并且 SWIG 正在为下面显示的枚举寻找最佳匹配:

C++%typemap(in) const Hello & { ... }%typemap(in) const enum SWIGTYPE & { ... }%typemap(in) enum SWIGTYPE & { ... }%typemap(in) SWIGTYPE & { ... }%typemap(in) SWIGTYPE { ... } enum Hello {};const Hello &hi;

列表顶部的类型映射将被选择,不是因为它首先被定义,而是因为它与被包装的类型最接近。如果上述列表中的任何类型映射未定义,则列表中的下一个将具有优先权。

探索默认类型映射的最佳方法是查看已为特定语言模块定义的类型映射。类型映射定义通常在 SWIG 库中的一个文件中找到,例如 java.swg、csharp.swg 等。然而,对于许多目标语言,类型映射隐藏在复杂的宏后面,因此查看默认类型映射的最佳方法,或与此相关的任何类型映射都是通过在任何接口文件上运行 swig -E 来查看预处理输出。最后,查看 typemap 匹配规则的最佳方法是通过稍后介绍的调试 typemap 模式匹配选项。

兼容性说明:默认类型映射匹配规则在 SWIG-2.0.0 中从一个稍微简单的方案进行了修改,以匹配当前的 C++ 类模板部分特化匹配规则。

11.3.4 多参数 typemap

当指定多参数 typemap 时,它们优先于为单个类型指定的任何类型映射。例如:

C++%typemap(in) (char *buffer, int len) { // typemap 1} %typemap(in) char *buffer { // typemap 2} void foo(char *buffer, int len, int count); // (char *buffer, int len)void bar(char *buffer, int blah); // char *buffe

多参数类型映射在匹配方式上也更具限制性。目前,第一个参数遵循上一节中描述的匹配规则,但所有后续参数必须完全匹配。

11.3.5 匹配规则与 C++ 模板的比较

对于那些非常熟悉 C++ 模板的人来说,比较类型映射匹配规则和模板类型推导是很有趣的。考虑的两个方面首先是默认类型映射及其与部分模板专业化的相似性,其次是非默认类型映射及其与完整模板专业化的相似性。

对于默认 (SWIGTYPE) 类型映射,规则受 C++ 类模板部分特化的启发。例如,给定 T const& 的部分特化:

C++template <typename T> struct X { void a(); };template <typename T> struct X< T const& > { void b(); };

完整(非特化)模板与大多数类型匹配,例如:

C++X< int & > x1; x1.a();

并且以下所有匹配T const& 部分特化:

C++X< int *const& > x2; x2.b();X< int const*const& > x3; x3.b();X< int const& > x4; x4.b();

现在,仅给出这两个默认类型映射,其中 T 类似于 SWIGTYPE:

C++%typemap(...) SWIGTYPE { ... } %typemap(...) SWIGTYPE const& { ... }

通用默认类型映射 SWIGTYPE与大多数类型一起使用,例如

C++int &

并且以下都匹配 SWIGTYPE const& 类型映射,就像部分模板匹配一样:

C++int *const& int const*const& int const&

请注意,模板和类型映射匹配规则对于所有默认类型映射并不相同,例如,对于数组。

对于非默认类型映射,人们可能希望 SWIG 遵循完全专用的模板规则。这几乎是这种情况,但并非完全如此。考虑一个与早期部分专门化模板非常相似的例子,但这次有一个完全专门化的模板:

C++template <typename T> struct Y { void a(); };template <> struct Y< int const & > { void b(); };

只有一种类型与专用模板完全匹配:

C++Y< int & > y1; y1.a();Y< int *const& > y2; y2.a();Y< int const *const& > y3; y3.a();Y< int const& > y4; y4.b(); // fully specialized match

给定的类型映射与上面声明的模板使用的类型相同,其中 T 再次类似于 SWIGTYPE:

C++%typemap(...) SWIGTYPE { ... } %typemap(...) int const& { ... }

非默认类型映射和完全专用的单参数模板之间的比较结果是相同的,因为只有一种类型将匹配非默认类型映射:

C++int &int *const&int const*const&int const& // matches non-default typemap int const&

但是,如果改用非常量类型:

C++%typemap(...) SWIGTYPE { ... } %typemap(...) int & { ... }

那么模板匹配有明显的不同,因为 const 和非常量类型都匹配 typemap:

C++int & // matches non-default typemap int &int *const&int const*const&int const& // matches non-default typemap int &

还有其他细微的差异,例如 typedef 处理,但至少应该清楚,typemap 匹配规则与专门模板处理的规则相似。

11.3.6 调试 typemap 模式匹配

有两个有用的调试命令行选项可用于调试类型映射,-debug-tmsearch 和 -debug-tmused 。

该 -debug-tmsearch 选项是用于调试类型映射搜索一个详细选项。这对于观察正在运行的模式匹配过程和调试使用了哪些类型映射非常有用。该选项显示在成功进行模式匹配之前要查找的所有类型映射和类型。由于显示包括对包装所需的每种类型的搜索,因此显示的信息量可能很大。通常,您会手动搜索您感兴趣的特定类型的显示信息。

例如,考虑已经涵盖的 Typedef 缩减部分中使用的一些代码:

C++typedef int Integer;typedef Integer Row4[4];void foo(Row4 rows[10]);

下面显示了“in”类型映射的调试输出示例:

Plain Textswig -perl -debug-tmsearch example.i...example.h:3: Searching for a suitable 'in' typemap for: Row4 rows[10] Looking for: Row4 rows[10] Looking for: Row4 [10] Looking for: Row4 rows[ANY] Looking for: Row4 [ANY] Looking for: Integer rows[10][4] Looking for: Integer [10][4] Looking for: Integer rows[ANY][ANY] Looking for: Integer [ANY][ANY] Looking for: int rows[10][4] Looking for: int [10][4] Looking for: int rows[ANY][ANY] Looking for: int [ANY][ANY] Looking for: SWIGTYPE rows[ANY][ANY] Looking for: SWIGTYPE [ANY][ANY] Looking for: SWIGTYPE rows[ANY][] Looking for: SWIGTYPE [ANY][] Looking for: SWIGTYPE *rows[ANY] Looking for: SWIGTYPE *[ANY] Looking for: SWIGTYPE rows[ANY] Looking for: SWIGTYPE [ANY] Looking for: SWIGTYPE rows[] Looking for: SWIGTYPE [] Using: %typemap(in) SWIGTYPE []...

表明 SWIG 提供的最佳默认匹配是 SWIGTYPE [] 类型映射。如示例所示,成功匹配以以下简化格式之一显示使用的类型映射源,包括类型映射方法、类型和可选名称:

• 使用: %typemap(method) type name

• 使用:%typemap(method) type name = type2 name2

• 使用: %apply type2 name2 { type name }

此信息可能满足您的调试需求,但是,您可能需要进一步分析。如果您接下来使用 -E 选项调用 SWIG以显示预处理的输出,并搜索使用的特定类型映射,您将找到完整的类型映射内容(下面显示的 Python 示例):

C++%typemap(in, noblock=1) SWIGTYPE [] (void *argp = 0, int res = 0) { res = SWIG_ConvertPtr($input, &argp, $descriptor, $disown | 0 ); if (!SWIG_IsOK(res)) { SWIG_exception_fail(SWIG_ArgError(res), "in method '" "$symname" "', argument " "$argnum"" of type '" "$type""'"); } $1 = ($ltype)(argp); }

为 foo 包装器生成的代码将包含类型映射的片段,并扩展了特殊变量。本章的其余部分需要阅读才能完全理解所有这些,但是,可以在下面看到上述类型映射生成代码的相关部分:

C++SWIGINTERN PyObject *_wrap_foo(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { ... void *argp1 = 0 ; int res1 = 0; ... res1 = SWIG_ConvertPtr(obj0, &argp1, SWIGTYPE_p_a_4__int, 0 | 0); if (!SWIG_IsOK(res1)) { SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "foo" "', argument " "1"" of type '" "int [10][4]""'") ; } arg1 = (int (*)[4])(argp1); ... }

除非确实存在匹配的多参数类型映射,否则不会提及搜索多参数类型映射。例如,前面多参数部分中代码的输出如下:

Plain Text...example.h:39: Searching for a suitable 'in' typemap for: char *buffer Looking for: char *buffer Multi-argument typemap found... Using: %typemap(in) (char *buffer, int len)...

调试的第二个选项是 -debug-tmused,它显示使用的类型映射。此选项是 -debug-tmsearch 选项的不太详细的版本,因为它仅在单独的一行中显示每个成功找到的类型映射。输出显示类型和名称(如果存在),括号中的类型映射方法,然后是 -debug-tmsearch 选项输出的相同简化格式中使用的实际类型映射。以下是本节开头的调试示例代码的输出。

Plain Text$ swig -perl -debug-tmused example.iexample.h:3: Typemap for Row4 rows[10] (in) : %typemap(in) SWIGTYPE []example.h:3: Typemap for Row4 rows[10] (typecheck) : %typemap(typecheck) SWIGTYPE *example.h:3: Typemap for Row4 rows[10] (freearg) : %typemap(freearg) SWIGTYPE []example.h:3: Typemap for void foo (out) : %typemap(out) void

现在,考虑以下接口文件:

C++%module example %{ void set_value(const char* val) {} %} %typemap(check) char *NON_NULL { if (!$1) { /* ... 错误处理 ... */ } } // 使用默认值指针处理而不是字符串%apply SWIGTYPE * { const char* val, const char* another_value } %typemap(check) const char* val = char* NON_NULL; %typemap(arginit, noblock=1) const char* val { $1 = ""; } void set_value(const char* val);

和输出调试:

Plain Textswig -perl5 -debug-tmused example.iexample.i:21: Typemap for char const *val (arginit) : %typemap(arginit) char const *valexample.i:21: Typemap for char const *val (in) : %apply SWIGTYPE * { char const *val }example.i:21: Typemap for char const *val (typecheck) : %apply SWIGTYPE * { char const *val }example.i:21: Typemap for char const *val (check) : %typemap(check) char const *val = char *NON_NULLexample.i:21: Typemap for char const *val (freearg) : %apply SWIGTYPE * { char const *val }example.i:21: Typemap for void set_value (out) : %typemap(out) void

可以注意到以下关于显示内容的观察结果(同样适用于-debug-tmsearch):

• 显示了相关的类型映射,但对于类型映射复制,会显示适当的 %typemap 或 %apply,例如,“check”和“in”类型映射。

• 未显示类型映射修饰符,例如“ arginit ”类型映射中的 noblock=1 修饰符。

• 确切的 %apply 语句可能与实际代码中的内容不同。例如,const char* another_value 未显示,因为它在此处不相关。此外,类型的显示方式可能略有不同—— char const * 而不是 const char*。

11.4 代码生成规则

本节描述了将类型映射代码插入到生成的包装器代码中的规则。

11.4.1 范围

当 typemap 像这样定义时:

C++%typemap(in) int { $1 = PyInt_AsLong($input); }

使用新的块作用域将类型映射代码插入到包装函数中。换句话说,包装器代码将如下所示:

C++wrap_whatever() { ... // Typemap code { arg1 = PyInt_AsLong(obj1); } ...}

因为 typemap 代码包含在它自己的块中,所以在 typemap 执行期间声明临时变量是合法的。例如:

C++%typemap(in) short { long temp; /* Temporary value */ if (Tcl_GetLongFromObj(interp, $input, &temp) != TCL_OK) { return TCL_ERROR; } $1 = (short) temp;}

当然,您在类型映射中声明的任何变量都会在类型映射代码执行后立即销毁(它们对包装函数的其他部分或可能使用相同变量名称的其他类型映射不可见)。

有时,会使用一些替代形式指定类型映射代码。例如:

C++%typemap(in) int "$1 = PyInt_AsLong($input);"; %typemap(in) int %{ $1 = PyInt_AsLong($input); %} %typemap(in, noblock=1) int { $1 = PyInt_AsLong($input); }

这三种形式主要用于化妆品——指定的代码在发出时不包含在块范围内。这有时会导致看起来不太复杂的包装函数。请注意,三个类型映射中只有第三个具有通过 SWIG 预处理器传递的类型映射代码。

11.4.2 声明新的局部变量

有时,声明一个存在于整个包装函数范围内的新局部变量很有用。一个很好的例子可能是您想要在其中编组字符串的应用程序。假设你有一个这样的 C++ 函数

C++int foo(std::string *s);

并且您想将目标语言中的本机字符串作为参数传递。例如,在 Perl 中,您希望函数像这样工作:

Perl$x = foo("Hello World");

为此,您不能仅将原始 Perl 字符串作为 std::string * 参数传递。相反,您必须创建一个临时的 std::string对象,将 Perl 字符串数据复制到其中,然后传递一个指向该对象的指针。为此,只需使用一个额外的参数指定类型映射,如下所示:

Perl%typemap(in) std::string * (std::string temp) { unsigned int len; char *s; s = SvPV($input, len); /* Extract string data */ temp.assign(s, len); /* Assign to temp */ $1 = &temp; /* Set argument to point to temp */}

在这种情况下,temp 成为整个包装函数范围内的局部变量。例如:

C++wrap_foo() { std::string temp; <--- Declaration of temp goes here ... /* Typemap code */ { ... temp.assign(s, len); ... } ...}

当您将 temp 设置为一个值时,它会在包装函数的持续时间内持续存在,并在退出时自动清除。

在同一个声明中使用多个涉及局部变量的类型映射是完全安全的。例如,您可以将函数声明为:

C++void foo(std::string *x, std::string *y, std::string *z);

这是安全的处理,因为 SWIG 实际上通过附加参数编号后缀来重命名所有局部变量引用。因此,生成的代码实际上如下所示:

C++wrap_foo() { int *arg1; /* Actual arguments */ int *arg2; int *arg3; std::string temp1; /* Locals declared in the typemap */ std::string temp2; std::string temp3; ... { char *s; unsigned int len; ... temp1.assign(s, len); arg1 = *temp1; } { char *s; unsigned int len; ... temp2.assign(s, len); arg2 = &temp2; } { char *s; unsigned int len; ... temp3.assign(s, len); arg3 = &temp3; } ...}

有一个例外:如果变量名称以 _global_ 前缀开头,则不会附加参数编号。这样的变量可以在整个生成的包装函数中使用。例如,上面的类型映射可以重写为使用 _global_temp 而不是temp,然后生成的代码将包含一个_global_temp 变量而不是 temp1、 temp2 和 temp3:

C++%typemap(in) std::string * (std::string _global_temp) { ... 同上 ... }

某些类型映射不识别局部变量(或者它们可能根本不适用)。目前,只有适用于参数转换的类型映射支持此(输入类型映射,例如 “in” 类型映射)。

笔记:

为多种类型声明类型映射时,每种类型都必须有自己的局部变量声明。

C++%typemap(in) const std::string *, std::string * (std::string temp) // 不!// 只有 std::string * 有一个局部变量// const std::string * 没有 (oops) .... %typemap(in) const std::string * (std::string temp), std: :string * (std::string temp) // 正确....

11.4.3 特殊变量

在所有类型映射中,扩展了以下特殊变量。这绝不是一个完整的列表,因为某些目标语言具有额外的特殊变量,这些变量记录在语言特定的章节中。

多变的

意义

$n

对应于 typemap 模式中的类型 n 的一个 C 局部变量。

$argnum

参数编号。仅在与参数转换相关的类型映射中可用

$n_name

参数名称

$n_type

n 类型的真实C 数据类型。

$n_ltype

n 类型的 ltype

$n_mangle

n 类型的重整形式。例如 _p_Foo

$n_descriptor

类型 n 的类型描述符结构。例如 SWIGTYPE_p_Foo。这主要在与运行时类型检查器(稍后描述)交互时使用。

$*n_type

删除了一个指针的 n 类型的真实 C 数据类型。

$*n_ltype

删除了一个指针的 n 类型的 ltype 。

$*n_mangle

删除了一个指针的 n 类型的重整形式。

$*n_descriptor

删除了一个指针的类型 n 的类型描述符结构。

$&n_type

类型为 n 的真实 C 数据类型,添加了一个指针。

$&n_ltype

添加了一个指针的 n 类型的 ltype 。

$&n_mangle

添加了一个指针的 n 类型的重整形式。

$&n_descriptor

类型 n 的类型描述符结构,添加了一个指针。

$n_basetype

去除了所有指针和限定符的基本类型名。

在表中,$ n指的是类型映射规范中的特定类型。例如,如果你写这个

C++%typemap(in) int *INPUT { }

那么 $1 指的是 int *INPUT。如果你有这样的类型图,

C++%typemap(in) (int argc, char *argv[]) { ... }

那么 1 指的是 int argc, 2 指的是 char *argv[]。

与类型和名称相关的替换总是填充来自匹配的实际代码的值。当类型映射可能匹配多个 C 数据类型时,这很有用。例如:

C++%typemap(in) int, short, long { $1 = ($1_ltype) PyInt_AsLong($input); }

在这种情况下,$1_ltype 被替换为实际匹配的数据类型。

发出类型映射代码时,特殊变量1和2的 C/C++ 数据类型始终是“ltype”。“ltype”只是一种可以合法出现在 C 赋值操作左侧的类型。下面是几个类型和 ltypes 的例子:

Plain Texttype ltype------ ----------------int intconst int intconst int * int *int [4] int *int [4][5] int (*)[5]

在大多数情况下,ltype 只是去掉了限定符的 C 数据类型。此外,数组被转换为指针。

&1_type 和

如有必要,在声明局部变量时也可以使用类型相关的替换。例如:

Plain Text%typemap(in) int * ($*1_type temp) { temp = PyInt_AsLong($input); $1 = &temp; }

以这种方式声明局部变量有一个注意事项。如果您使用诸如$1_ltype temp 之类的类型替换声明局部变量,则它不会像您对数组和某些类型的指针所期望的那样工作。例如,如果你写了这个,

C++%typemap(in) int [10][20] { $1_ltype temp; }

那么 temp 的声明将被扩展为

C++int (*)[20] temp;

这是非法的 C 语法,无法编译。由于扩展和处理类型映射代码的方式,目前在 SWIG 中没有直接的方法来解决此问题。但是,一种可能的解决方法是简单地选择一种替代类型,例如 void * 并在需要时使用强制转换来获取正确的类型。例如:

C++%typemap(in) int [10][20] { void *temp; ... (($1_ltype) temp)[i][j] = x; /* 设置一个值 */ ... }

另一种仅适用于数组的方法是使用 $1_basetype 替换。例如:

C++%typemap(in) int [10][20] { $1_basetype temp[10][20]; ... temp[i][j] = x; /* 设置一个值 */ ... }

11.4.4 特殊变量宏

特殊变量宏类似于宏函数,因为它们采用一个或多个用于宏扩展的输入参数。它们看起来像宏/函数调用,但在宏名称前使用了特殊的变量 $ 前缀。请注意,与普通宏不同,扩展不是由预处理器完成的,而是在 SWIG 解析/编译阶段完成的。以下特殊变量宏可用于所有语言模块。

11.4.4.1 $descriptor(type)

该宏扩展到 type.c 中指定的任何 C/C++ 类型的类型描述符结构。它的行为类似于上面描述的 1_descriptor 特殊变量,不同之处在于要扩展的类型是从宏参数中获取的,而不是从类型映射类型中推断出来的。例如,descriptor(std::vector<int> *) 将扩展为 SWIGTYPE_p_std__vectorT_int_t。此宏主要用于脚本目标语言,稍后将在运行时类型检查器使用部分进行演示。

11.4.4.2 $typemap(method, typepattern)

这个宏使用前面描述的模式匹配规则来查找,然后用匹配的类型映射中的代码替换特殊变量宏。要搜索的类型映射由参数指定,其中方法是类型映射方法名称,类型模式是根据定义类型映射部分中的 %typemap 规范的类型模式。

匹配类型映射中的特殊变量被扩展为匹配类型映射类型的变量,而不是调用宏的类型映射。实际上,这个宏在脚本目标语言中几乎没有用处。它主要用于静态类型化的目标语言,作为获取给定 C/C++ 类型的目标语言类型的一种方式,更常见的是仅当 C++ 类型是模板参数时。

下面的示例仅适用于 C#,并使用了 C# 章节中记录的一些类型映射方法名称,但它显示了一些可能的语法变体。

C++%typemap(cstype) unsigned long "uint" %typemap(cstype) unsigned long bb "bool" %typemap(cscode) BarClass %{ void foo($typemap(cstype, unsigned long aa) var1, $typemap(cstype, unsigned long bb) var2, $typemap(cstype, (unsigned long bb)) var3, $typemap(cstype, unsigned long) var4) { // 做某事 } %}

结果如下展开

C++%typemap(cstype) unsigned long "uint" %typemap(cstype) unsigned long bb "bool" %typemap(cscode) BarClass %{ void foo(uint var1, bool var2, bool var3, uint var4) { // 做点什么 } %}

11.4.5 特殊变量和类型映射属性

自 SWIG-3.0.7 起,typemap 属性还将扩展特殊变量和特殊变量宏。

示例用法显示了 'out' 属性(特定于 C#)以及主要类型映射主体中的扩展:

C++%typemap(ctype, out="$*1_ltype") unsigned int& "$*1_ltype"

当$*1_ltype扩展为 unsigned int 时,等效于以下内容:

C++%typemap(ctype, out="unsigned int") unsigned int& "unsigned int"

11.4.6 特殊变量结合特殊变量宏

特殊变量也可以在特殊变量宏中使用。特殊变量在特殊变量宏中使用之前会被扩展。

考虑以下 C# 类型映射:

C++%typemap(cstype) unsigned int "uint" %typemap(cstype, out="$typemap(cstype, $*1_ltype)") unsigned int& "$typemap(cstype, $*1_ltype)"

特殊变量首先被扩展,因此上面的等价于:

C++%typemap(cstype) unsigned int "uint" %typemap(cstype, out="$typemap(cstype, unsigned int)") unsigned int& "$typemap(cstype, unsigned int)"

然后扩展为:

C++%typemap(cstype) unsigned int "uint" %typemap(cstype, out="uint") unsigned int& "uint"

11.5 常用的typemap方法

语言模块识别的类型映射集可能会有所不同。但是,以下类型映射方法几乎是通用的:

11.5.1 "in" 类型映射

“in”类型映射用于将函数参数从目标语言转换为 C。例如:

C++%typemap(in) int { $1 = PyInt_AsLong($input); }

以下特殊变量可用:

C++$input - 包含要转换的值的输入对象。$symname - 被包装的函数/方法的名称

这可能是最常见的重新定义类型映射,因为它可用于实现自定义转换。

此外,“in”类型映射允许指定转换参数的数量。该 numinputs 属性有助于此。例如:

C++// 忽略参数。%typemap(in, numinputs=0) int *out (int temp) { $1 = &temp; }

此时,只能转换零个或一个参数。当 numinputs 设置为 0 时,该参数实际上被忽略并且无法从目标语言提供。在进行 C/C++ 调用时仍然需要该参数,并且上面的类型映射显示所使用的值是从名为 temp的本地声明变量中获得的。通常不指定 numinputs,因此默认值为 1,即从目标语言到 C/C++ 调用时使用的参数数量是一对一的映射。多参数类型映射提供了一个类似的概念,其中从目标语言映射到 C/C++ 的参数数量可以更改为多个相邻的 C/C++ 参数。

兼容性说明:指定 numinputs=0 与旧的“ignore”类型映射相同。

11.5.2 "typecheck" 类型映射

“类型检查”类型映射用于支持重载的函数和方法。它只是检查参数以查看它是否与特定类型匹配。例如:

C++%typemap(typecheck, precedence=SWIG_TYPECHECK_INTEGER) int { $1 = PyInt_Check($input) ? 1:0;}

对于类型检查,$1 变量始终是一个简单的整数,根据输入参数是否为正确的类型设置为 1 或 0。如果输入参数是正确的类型,则设置为 1,否则设置为 0。

如果你定义了新的“in”类型映射并且你的程序使用了重载方法,你还应该定义一个“类型检查”类型映射的集合。更多详细信息请参见类型映射和重载部分。

11.5.3 "out" 类型映射

“out”类型映射用于将函数/方法的返回值从 C 转换为目标语言。例如:

C++%typemap(out) int { $result = PyInt_FromLong($1); }

以下特殊变量可用。

C++$result - 结果对象返回到目标语言。$symname - 被包装的函数/方法的名称

“out”类型映射支持称为“optimal”的可选属性标志。这是用于代码优化的,在按值返回时的优化代码生成部分中有详细说明。

11.5.4 "arginit" 类型映射

“arginit”类型映射用于在任何转换发生之前设置函数参数的初始值。这通常不是必需的,但在高度专业化的应用程序中可能很有用。例如:

C++// 在任何转换发生之前将参数设置为 NULL %typemap(arginit) int *data { $1 = NULL; }

11.5.5 “default”类型映射

“默认”类型映射用于将参数转换为默认参数。例如:

C++%typemap(default) int flags { $1 = DEFAULT_FLAGS; } ... int foo(int x, int y, int flags);

此类型映射的主要用途是更改默认参数的包装或在不支持它们的语言(如 C)中指定默认参数。不支持可选参数的目标语言(例如 Java 和 C#)实际上会忽略此类型映射指定的值,因为必须提供所有参数。

一旦将默认类型映射应用于参数,后面的所有参数都必须具有默认值。有关默认参数包装的更多信息,请参阅默认/可选参数部分。

11.5.6 “check”类型图

“检查”类型映射用于在参数转换期间提供值检查代码。在转换参数应用类型映射。例如:

C++%typemap(check) int positive { if ($1 <= 0) { SWIG_exception(SWIG_ValueError, "Expected positive value."); } }

11.5.7 "argout" 类型映射

“argout”类型映射用于从参数返回值。这最常用于为需要返回多个值的 C/C++ 函数编写包装器。“argout”类型映射几乎总是与“in”类型映射结合使用——可能会忽略输入值。例如:

C++/* 设置输入参数指向一个临时变量 */ %typemap(in, numinputs=0) int *out (int temp) { $1 = &temp; } %typemap(argout) int *out { // 将输出值 $1 附加到 $result ... }

以下特殊变量可用。

C++$result - 结果对象返回到目标语言。$input - 传递的原始输入对象。$symname - 被包装的函数/方法的名称

提供给“argout”类型映射的代码总是放在“out”类型映射之后。如果使用多个返回值,则额外的返回值通常会附加到函数的返回值中。

有关示例,请参阅typemaps.i库文件。

11.5.8 "freearg" 类型映射

“freearg”类型映射用于清理参数数据。它仅在参数可能已分配需要在包装器函数退出时清除的资源时使用。“freearg”类型映射通常会清理“in”类型映射分配的参数资源。例如:

C++// 获取整数列表%typemap(in) int *items { int nitems = Length($input); $1 = (int *) malloc(sizeof(int)*nitems); } // 释放列表%typemap(freearg) int *items { free($1); }

“freearg”类型映射插入在包装函数的末尾,就在控制返回到目标语言之前。这段代码也被放置在一个特殊的变量$cleanup 中,只要包装函数需要提前中止,它就可以在其他类型映射中使用。

11.5.9 “newfree” 类型图

“newfree”类型映射与%newobject 指令结合使用,用于释放函数返回结果使用的内存。例如:

C++%typemap(newfree) string * { delete $1; } %typemap(out) string * { $result = PyString_FromString($1->c_str()); } ... %newobject foo; ...string *foo();

有关更多详细信息,请参阅对象所有权和 %newobject。

11.5.10 “ret” 类型映射

“ret”类型映射不经常使用,但对于与返回类型相关的任何事情都很有用,例如资源管理,返回值错误检查等。通常这都可以在“out”类型映射中完成,但有时使用原封不动的“out”类型映射代码并使用“ret”类型映射中的代码添加到生成的代码中是很方便的。一种这样的情况是内存清理。例如,定义 stringheap_t 类型表示必须删除返回的内存,定义 string_t 类型表示不能删除返回的内存。

C++%typemap(ret) stringheap_t %{ free($1); %} typedef char * string_t; typedef char * stringheap_t; string_t MakeString1(); stringheap_t MakeString2();

上面的“ret”类型映射仅用于MakeString2,但两个函数都将使用 SWIG 提供的 char * 的默认“out”类型映射。上面的代码将确保在所有目标语言中释放适当的内存,因为不需要提供自定义的“out”类型映射(涉及目标语言特定代码)。

这种方法是使用“newfree”类型映射和 %newobject 的替代方法,因为不需要列出所有需要清理内存的函数,它纯粹是在类型上完成的。

11.5.11 “memberin” 类型映射

“memberin”类型映射用于将数据从已转换的输入值复制到结构成员中。它通常用于处理数组成员和其他特殊情况。例如:

C++%typemap(memberin) int [4] { memmove($1, $input, 4*sizeof(int)); }

很少需要编写“memberin”类型映射——SWIG 已经为数组、字符串和其他对象提供了默认实现。

11.5.12 "varin" 类型映射

“varin”类型映射用于将目标语言中的对象转换为 C,以便分配给 C/C++ 全局变量。这是特定于实现的。

11.5.13 “varout”类型映射

“varout”类型映射用于在读取 C/C++ 全局变量时将 C/C++ 对象转换为目标语言中的对象。这是特定于实现的。

11.5.14 “throws”类型映射

“throws”类型映射仅在 SWIG 使用异常规范解析 C++ 方法或将 %catches 功能附加到方法时使用。它提供了一种默认机制来处理声明了它们将抛出的异常的 C++ 方法。此类型映射的目的是将 C++ 异常转换为目标语言中的错误或异常。它与其他类型映射略有不同,因为它基于异常类型而不是参数或变量的类型。例如:

C++%typemap(throws) const char * %{ PyErr_SetString(PyExc_RuntimeError, $1); SWIG_fail; %} void bar() throw (const char *);

从下面生成的代码中可以看出,SWIG 生成了一个异常处理程序,其中 catch 块包含“throws”类型映射内容。

C++...try { bar(); } catch(char const *_e) { PyErr_SetString(PyExc_RuntimeError, _e); SWIG_fail; } ...

请注意,如果您的方法没有异常规范但它们确实抛出了异常,则 SWIG 无法知道如何处理它们。有关处理这些问题的巧妙方法,请参阅 %exception部分的异常处理。

11.6 一些 typemap 类型映射示例

本节包含几个示例。有关更多示例,请参阅语言模块文档。

11.6.1 数组的类型映射

类型映射的一个常见用途是为 C 数组提供支持,这些数组既作为函数的参数也作为结构成员出现。

例如,假设你有一个这样的函数:

C++void set_vector(int type, float value[4]);

如果你想把float value[4]作为一个浮点数列表处理,你可以写一个类似于这样的类型映射:

Bash %typemap(in) float value[4] (float temp[4]) { int i; if (!PySequence_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expected a sequence"); SWIG_fail; } if (PySequence_Length($input) != 4) { PyErr_SetString(PyExc_ValueError, "Size mismatch. Expected 4 elements"); SWIG_fail; } for (i = 0; i < 4; i++) { PyObject *o = PySequence_GetItem($input, i); if (PyNumber_Check(o)) { temp[i] = (float) PyFloat_AsDouble(o); } else { PyErr_SetString(PyExc_ValueError, "Sequence elements must be numbers"); SWIG_fail; } } $1 = temp;}

在此示例中,变量 temp 在 C 堆栈上分配一个小数组。然后 typemap 填充这个数组并将它传递给底层的 C 函数。

在 Python 中使用时,typemap 允许以下类型的函数调用:

Python>>> set_vector(type, [ 1, 2.5, 5, 20 ])

如果你想将 typemap 推广到所有维度的数组,你可以这样写:

C++%typemap(in) float value[ANY] (float temp[$1_dim0]) { int i; if (!PySequence_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expected a sequence"); SWIG_fail; } if (PySequence_Length($input) != $1_dim0) { PyErr_SetString(PyExc_ValueError, "Size mismatch. Expected $1_dim0 elements"); SWIG_fail; } for (i = 0; i < $1_dim0; i++) { PyObject *o = PySequence_GetItem($input, i); if (PyNumber_Check(o)) { temp[i] = (float) PyFloat_AsDouble(o); } else { PyErr_SetString(PyExc_ValueError, "Sequence elements must be numbers"); SWIG_fail; } } $1 = temp;}

在这个例子中,特殊变量$1_dim0被扩展为实际的数组维度。多维数组可以以类似的方式进行匹配。例如:

C++%typemap(in) float matrix[ANY][ANY] (float temp[$1_dim0][$1_dim1]) { ... 转换一个二维数组 ... }

对于大型数组,使用如图所示的临时变量在堆栈上分配存储可能是不切实际的。要使用堆分配的数据,可以使用以下技术。

Bash%typemap(in) float value[ANY] { int i; if (!PySequence_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expected a sequence"); SWIG_fail; } if (PySequence_Length($input) != $1_dim0) { PyErr_SetString(PyExc_ValueError, "Size mismatch. Expected $1_dim0 elements"); SWIG_fail; } $1 = (float *) malloc($1_dim0*sizeof(float)); for (i = 0; i < $1_dim0; i++) { PyObject *o = PySequence_GetItem($input, i); if (PyNumber_Check(o)) { $1[i] = (float) PyFloat_AsDouble(o); } else { free($1); PyErr_SetString(PyExc_ValueError, "Sequence elements must be numbers"); SWIG_fail; } }}%typemap(freearg) float value[ANY] { if ($1) free($1);}

在这种情况下,使用 malloc 分配一个数组。该 freearg 然后类型表是用来释放参数的函数被调用后。

数组类型映射的另一个常见用途是为数组结构成员提供支持。由于 C 中指针和数组之间的细微差别,您不能只是“分配”给数组结构成员。相反,您必须显式地将元素复制到数组中。例如,假设您有这样的结构:

C++struct SomeObject { float value[4]; ...};

当 SWIG 运行时,它不会生成任何代码来设置vec 成员。您甚至可能会收到如下警告消息:

Plain Text$ swig -python example.iexample.i:10: Warning 462: Unable to set variable of type float [4].

这些警告消息表明 SWIG 不知道您想如何设置 vec 字段。

要解决此问题,您可以提供一个特殊的“memberin”类型映射,如下所示:

C++%typemap(memberin) float [ANY] { int i; for (i = 0; i < $1_dim0; i++) { $1[i] = $input[i]; } }

memberin 类型映射用于从已经从目标语言转换为 C 的数据中设置结构成员。在这种情况下,$input是存储转换后输入数据的局部变量。这个类型映射然后将此数据复制到结构中。

当与早期的数组类型映射结合使用时,“in”和“memberin”类型映射的组合允许以下用法:

Python>>> s = SomeObject() >>> sx = [1, 2.5, 5, 10]

与结构成员输入相关,可能需要将结构成员作为一种新的对象返回。例如,在这个例子中,你会得到非常奇怪的程序行为,其中结构成员可以很好地设置,但读取成员只是返回一个指针:

Shell>>> s = SomeObject() >>> sx = [1, 2.5, 5, 10] >>> print s.x_1008fea8_p_float >>>

要解决此问题,您可以编写一个“out”类型映射。例如:

C++%typemap(out) float [ANY] { int i; $result = PyList_New($1_dim0); for (i = 0; i < $1_dim0; i++) { PyObject *o = PyFloat_FromDouble((double) $1[i]); PyList_SetItem($result, i, o); } }

现在,您会发现会员访问非常好:

Python>>> s = SomeObject()>>> s.x = [1, 2.5, 5, 10]>>> print s.x[ 1, 2.5, 5, 10]

兼容性说明: SWIG1.1 用于提供特殊的“memberout”类型映射。然而,它大多没用,后来被淘汰了。要返回结构成员,只需使用“out”类型映射。

11.6.2 用类型映射实现约束

类型映射的一个特别有趣的应用是参数约束的实现。这可以通过“check”类型映射来完成。使用时,这允许您提供用于检查函数参数值的代码。例如:

C++%module math %typemap(check) double posdouble { if ($1 < 0) { croak("Expecting a positive number"); }} ...double sqrt(double posdouble);

这为您的包装函数提供了健全性检查。如果将负数传递给此函数,将引发 Perl 异常并且程序终止并显示错误消息。

这种检查在使用指针时特别有用。例如:

C++%typemap(check) Vector * { if ($1 == 0) { PyErr_SetString(PyExc_TypeError, "NULL Pointer not allowed"); SWIG_fail; } }

将阻止任何涉及 Vector * 的函数接受 NULL 指针。因此,SWIG 通常可以通过引发异常而不是盲目地将值传递给底层 C/C++ 程序来防止潜在的分段错误或其他运行时问题。

11.7 多种目标语言的类型图

类型映射中的代码通常依赖于语言,但是,许多目标语言支持相同的类型映射。为了区分不同语言的类型映射,应该使用预处理器。例如,Perl 和 Ruby 的“in”类型映射可以写成:

Shell#if defined(SWIGPERL) %typemap(in) int "$1 = ($1_ltype) SvIV($input);"#elif defined(SWIGRUBY) %typemap(in) int "$1 = NUM2INT($input);"#else #warning no "in" typemap defined#endif

完整的语言特定宏集在条件编译部分中定义。上面的示例还显示了对尚未支持的语言发出警告的常见方法。

兼容性说明:在 SWIG-1.1 中,可以通过将语言名称放在%typemap 指令中来区分不同的语言,例如,

C++%typemap(ruby, in) int "$1 = NUM2INT($input);" .

11.8 按值返回时的最佳代码生成

“out”类型映射是返回类型的主要类型映射。这个类型映射支持一个名为“optimal”的可选属性标志,用于减少临时变量和生成的代码量,从而使编译器有机会使用返回值优化来生成更快执行的代码。它只有在按值返回对象时才真正产生影响,并且在使用上有一些限制,稍后会解释。

当函数按值返回对象时,SWIG 会生成代码来实例化堆栈上的默认类型,然后将函数调用返回的值分配给它。然后在堆上制作此对象的副本,这就是最终从目标语言存储和使用的内容。考虑一个例子会更清楚。考虑通过 SWIG 运行以下代码:

C++%typemap(out) SWIGTYPE %{ $result = new $1_ltype((const $1_ltype &)$1);%} %inline %{#include <iostream>using namespace std; struct XX { XX() { cout << "XX()" << endl; } XX(int i) { cout << "XX(" << i << ")" << endl; } XX(const XX &other) { cout << "XX(const XX &)" << endl; } XX & operator =(const XX &other) { cout << "operator=(const XX &)" << endl; return *this; } ~XX() { cout << "~XX()" << endl; } static XX create() { return XX(0); }};%}

当按值返回对象时,显示的“out”类型映射是 C# 的默认类型映射。从 C#调用XX::create() 时,输出如下:

C#XX() XX(0) operator=(const XX &) ~XX() XX(const XX &) ~XX() ~XX()

请注意,正在创建三个对象以及一个分配。如果 XX::create() 方法是构造函数被调用的唯一时间,那不是很好吗?由于该方法按值返回,这要求很多,并且 SWIG 默认生成的代码使编译器无法使用返回值优化 (RVO)。但是,这是“out”类型映射中的“optimal”属性可以提供帮助的地方。如果 typemap 代码保持不变,并且只是像这样指定“optimal”属性:

C++%typemap(out, optimization="1") SWIGTYPE %{ $result = new $1_ltype((const $1_ltype &)$1); %}

然后当代码再次运行时,输出很简单:

C#XX(0) ~XX()

使用生成的代码最好地解释了“最佳”属性的工作原理。没有“最优”,生成的代码是:

C++SWIGEXPORT void * SWIGSTDCALL CSharp_XX_create() { void * jresult ; XX result; result = XX::create(); jresult = new XX((const XX &)result); return jresult;}

使用“optimal”属性,代码为:

C++SWIGEXPORT void * SWIGSTDCALL CSharp_XX_create() { void * jresult ; jresult = new XX((const XX &)XX::create()); return jresult;}

主要区别在于不再生成保存 XX::create() 返回值的结果临时变量,而是直接从 XX::create() 返回值进行复制构造函数调用。使用现代编译器实现 RVO,复制实际上并没有完成,事实上,对象根本不会在XX::create() 的堆栈上创建,它只是直接在堆上创建。在第一个实例中,类型映射中的 1 特殊变量被扩展为 result。在第二种情况下,1 被扩展为 XX::create() ,这基本上是“optimal”属性告诉 SWIG 要做的事情。

默认情况下不启用“optimal”属性优化,因为它有许多限制。首先,有些代码不能压缩成一个简单的调用来传递给复制构造函数。一种常见的情况是使用%exception时。考虑将以下 %exception 添加到示例中:

C++%exception XX::create() %{ try { $action } catch(const std::exception &e) { cout << e.what() << endl; } %}

SWIG 可以检测何时无法使用“最佳”属性并将忽略它,在这种情况下将发出以下警告:

Plain Textexample.i:28: Warning 474: Method XX::create() usage of the optimal attribute ignoredexample.i:14: Warning 474: in the out typemap as the following cannot be used to generateoptimal code: try { result = XX::create();} catch(const std::exception &e) { cout << e.what() << endl;}

应该清楚,上面的代码不能作为拷贝构造函数调用的参数,也就是 $1 替换。

其次,如果类型映射多次使用 $1,则将多次调用包装函数。显然这不是很理想。事实上,SWIG 试图检测到这一点,并会发出如下警告:

Plain Textexample.i:21: Warning 475: Multiple calls to XX::create() might be generated due toexample.i:7: Warning 475: optimal attribute usage in the out typemap.

然而,它并不总是正确的,例如当 $1 在一些注释掉的代码中时。

11.9 多参数类型映射

到目前为止,所提供的类型图都集中在处理单个值的问题上。例如,在函数调用中将单个输入对象转换为单个参数。然而,以这种方式难以处理某些转换问题。例如,请考虑本章开头的示例:

C++int foo(int argc, char *argv[]);

假设您想包装这个函数,以便它接受像这样的单个字符串列表:

Python>>> foo(["ale", "lager", "stout"])

为此,您不仅需要将字符串列表映射到 char *argv[],而且 int argc 的值由列表的长度隐式确定。只使用简单的类型映射,这种类型的转换是可能的,但非常痛苦。多参数类型映射在这种情况下有帮助。

多参数类型映射是一种转换规则,它指定如何将目标语言中的单个对象转换为 C/C++ 中的一组连续函数参数。例如,以下多参数映射执行上述示例所描述的转换:

C++%typemap(in) (int argc, char *argv[]) { int i; if (!PyList_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a list"); SWIG_fail; } $1 = PyList_Size($input); $2 = (char **) malloc(($1+1)*sizeof(char *)); for (i = 0; i < $1; i++) { PyObject *s = PyList_GetItem($input, i); if (!PyString_Check(s)) { free($2); PyErr_SetString(PyExc_ValueError, "List items must be strings"); SWIG_fail; } $2[i] = PyString_AsString(s); } $2[i] = 0;} %typemap(freearg) (int argc, char *argv[]) { if ($2) free($2);} /* Required for C++ method overloading */%typecheck(SWIG_TYPECHECK_STRING_ARRAY) (int argc, char *argv[]) { $1 = PyList_Check($input) ? 1 : 0;}

多参数映射总是通过用括号包围参数来指定,如图所示。例如:

C++%typemap(in) (int argc, char *argv[]) { ... }

在类型映射代码中,变量 1、2 等指的是映射中的每个类型。所有常用的替换都适用——只需在变量名上使用适当的 1 或 2 前缀(例如,2_type、1_ltype 等)

多参数类型映射始终优先于简单类型映射,并且 SWIG 始终执行最长匹配搜索。因此,您将获得以下行为:

C++%typemap(in) int argc { ... typemap 1 ... }%typemap(in) (int argc, char *argv[]) { ... typemap 2 ... }%typemap(in) (int argc, char *argv[], char *env[]) { ... typemap 3 ... } int foo(int argc, char *argv[]); // Uses typemap 2int bar(int argc, int x); // Uses typemap 1int spam(int argc, char *argv[], char *env[]); // Uses typemap 3

应该强调的是,多参数类型映射可以出现在函数声明中的任何地方,并且可以出现多次。例如,你可以这样写:

C++%typemap(in) (int scount, char *swords[]) { ... } %typemap(in) (int wcount, char *words[]) { ... } void search_words(int scount, char *swords[ ], int wcount, char *words[], int maxcount);

其他指令如 %apply 和 %clear 也适用于多参数映射。例如:

C++%apply (int argc, char *argv[]) { (int scount, char *swords[]), (int wcount, char *words[]) }; ... %clear (int scount, char *swords[]), (int wcount, char *words[]); ...

不要忘记还为重载函数提供合适的类型映射,例如上面为 foo 显示的 %typecheck。仅当函数在 C++ 中重载时才需要。

尽管多参数类型映射可能看起来像一个奇特的、很少使用的功能,但在几种情况下它们是有意义的。首先,假设您想包装类似于低级 read() 和 write() 系统调用。例如:

C++typedef unsigned int size_t; int read(int fd, void *rbuffer, size_t len); int write(int fd, void *wbuffer, size_t len);

照原样,使用这些函数的唯一方法是分配内存并将某种指针作为第二个参数传递——这个过程可能需要使用辅助函数。但是,使用多参数映射,可以将函数转换为更自然的东西。例如,您可以像这样编写类型映射:

C++// typemap for an outgoing buffer%typemap(in) (void *wbuffer, size_t len) { if (!PyString_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a string"); SWIG_fail; } $1 = (void *) PyString_AsString($input); $2 = PyString_Size($input);} // typemap for an incoming buffer%typemap(in) (void *rbuffer, size_t len) { if (!PyInt_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting an integer"); SWIG_fail; } $2 = PyInt_AsLong($input); if ($2 < 0) { PyErr_SetString(PyExc_ValueError, "Positive integer expected"); SWIG_fail; } $1 = (void *) malloc($2);} // Return the buffer. Discarding any previous return result%typemap(argout) (void *rbuffer, size_t len) { Py_XDECREF($result); /* Blow away any previous result */ if (result < 0) { /* Check for I/O error */ free($1); PyErr_SetFromErrno(PyExc_IOError); return NULL; } $result = PyString_FromStringAndSize($1, result); free($1);}

(注:在上面的例子中,result 和 result 是两个不同的变量。result 是一个由该函数返回的真正的 C数据类型。result 是返回到解释器的脚本语言的对象。)

现在,在脚本中,您可以编写简单地将缓冲区作为字符串传递的代码,如下所示:

Python>>> f = example.open("Makefile") >>> example.read(f, 40) 'TOP = ../..\nSWIG = $(TOP)/.' >>> example.read(f, 40) './swig\nSRCS = example.c\nTARGET ' >>> example.close(f) 0 >>> g = example.open("foo", example.O_WRONLY | example.O_CREAT, 0644) >>> example.write(g, "Hello world\n") 12 >>> example.write(g, "This is a test\n") 15 >>> example.close( g) 0 >>>

许多多参数类型映射问题也出现在执行矩阵计算的库中——尤其是当它们映射到低级 Fortran 或 C 代码时。例如,您可能有这样的函数:

C++int is_symmetric(double *mat, int rows, int columns);

在这种情况下,您可能希望将某种更高级别的对象作为矩阵传递。为此,您可以编写一个多参数类型映射,如下所示:

C++%typemap(in) (double *mat, int rows, int columns) { MatrixObject *a; a = GetMatrixFromObject($input); /* 以某种方式获取矩阵 */ /* 获取矩阵属性 */ $1 = GetPointer(a); $2 = GetRows(a); $3 = GetColumns(a); }

这种技术可用于挂钩脚本语言矩阵包,例如 Numeric Python。但是,还应该强调一些注意事项。例如,在跨语言时,您可能需要担心诸如行优先与列优先排序(并在需要时执行转换)等问题。请注意,多参数类型映射无法处理非连续的 C/C++ 参数;需要编写一个解决方法,例如帮助函数重新排序参数以使其连续。

11.10 Typemap 警告

可以向类型映射添加警告,以便在使用类型映射时 SWIG 生成警告消息。请参阅发出警告部分中的信息。

11.11 Typemap 片段

片段的主要目的是减少重复使用类型映射代码可能导致的代码膨胀。片段是可以被认为是类型映射的代码依赖项的代码片段。如果一个片段被多个类型映射使用,则片段内的代码片段仅生成一次。通常通过将类型映射代码移动到支持函数中,然后将支持函数放入片段中来减少代码膨胀。

例如,如果你有一个很长的 typemap

C++%typemap(in) MyClass * { MyClass *value = 0; ... 多行编组代码 ... $result = value; }

相同的编组代码经常在多个类型映射中重复,例如“in”、“varin”、“directorout”等。 SWIG 为每个需要类型映射代码的参数复制代码,很容易导致生成的代码中的代码膨胀。为了消除这种情况,定义一个包含公共编组代码的片段:

C++%fragment("AsMyClass", "header") { MyClass *AsMyClass(PyObject *obj) { MyClass *value = 0; ... many lines of marshalling code ... return value; }} %typemap(in, fragment="AsMyClass") MyClass * { $result = AsMyClass($input);} %typemap(varin, fragment="AsMyClass") MyClass * { $result = AsMyClass($input);}

当需要 MyClass 的“in”或“varin”类型映射时,将名为“AsMyClass”的片段的内容添加到生成代码中的“header”部分,然后发出类型映射代码。因此,方法AsMyClass将在调用它的任何类型映射代码之前生成到包装器代码中。

要定义片段,您需要片段名称、用于生成片段代码的部分名称以及代码本身。有关部分名称的完整列表,请参阅代码插入块。通常使用的部分名称是“header”。可以使用不同的分隔符:

C++%fragment("my_name", "header") %{ ... %} %fragment("my_name", "header") { ... } %fragment("my_name", "header") " ... "

这些遵循预处理分隔符部分中提到的常用预处理规则。以下是使用片段的一些规则和指南:

1. 一个片段只添加到包装代码一次。当使用上面的 MyClass * 类型映射并包装方法时:

C++void foo(MyClass *a, MyClass *b);

生成的代码将类似于:

C++MyClass *AsMyClass(PyObject *obj) { ... } void _wrap_foo(...) { .... arg1 = AsMyClass(obj1); arg2 = AsMyClass(obj2); ... foo(arg1, arg2); }

即使有重复的类型映射代码来处理a 和b,AsMyClass 方法也将只定义一次。

2. 一个片段应该只定义一次。如果有多个定义,则使用第一个定义。所有其他定义都被默默忽略。例如,如果你有

C++%fragment("AsMyClass", "header") { ...定义 1 ... } .... %fragment("AsMyClass", "header") { ...定义 2 ... }

仅使用第一个定义。通过这种方式,您可以通过在库 %include 之前定义您的片段来覆盖 SWIG 库中的默认片段。请注意,此行为与类型映射相反,其中最后定义/应用的类型映射占优势。Fragment 遵循先进先出的约定,因为它们是全局的,而 typemap 是本地专用的。

3. 片段名称不能包含逗号。

4. 一个片段可以使用一个或多个附加片段,例如:

C++%fragment("<limits.h>", "header") { %#include <limits.h> } %fragment("AsMyClass", "header", fragment="<limits.h>") { MyClass *AsMyClass (PyObject *obj) { MyClass *value = 0; ... 一些编组代码 ... if (ival < CHAR_MIN /*defined in <limits.h>*/) { ... } else { ... } ... return value; } }

在这种情况下,当发出“AsMyClass”片段时,它也会触发包含“<limits.h>”片段。

5. 一个片段可以依赖于许多其他片段,例如:

C++%fragment("bigfragment", "header", fragment="frag1", fragment="frag2", fragment="frag3") "";

使用“bigfragment”时,“frag1”、“frag2”、“frag3”三个依赖分片也被拉入。注意由于“bigframent”为空(空字符串-“”),所以不加任何代码本身,但仅触发其他片段的包含。

6. 一个 typemap 也可以使用多个片段,但由于语法不同,您需要在逗号分隔列表中指定依赖片段。考虑:

C++%typemap(in, fragment="frag1, frag2, frag3") {...}

这相当于:

C++%typemap(in, fragment="bigfragment") {...}

当与上面定义的“bigfragment”一起使用时。

7. 最后,您可以在生成的代码中的任何位置强制包含片段,如下所示:

C++%fragment("bigfragment");

例如,这在模板类中非常有用。

大多数读者可能希望跳过关于高级片段使用的接下来的两个小节,除非希望真正掌握一些在 SWIG 类型映射库部分中使用的强大但棘手的宏和片段用法。

11.11.1 片段类型特化

片段可以是专门的类型。语法如下:

C++%fragment("name", "header") { ...一个与类型无关的片段... } %fragment("name"{type}, "header") { ...一个依赖于类型的片段... }

其中type是 C/C++ 类型。和 typemap 一样,片段也可以在模板中使用,例如:

C++template <class T>struct A { %fragment("incode"{A<T>}, "header") { ... 'incode' specialized fragment ... } %typemap(in, fragment="incode"{A<T>}) { ... here we use the 'type specialized' fragment "incode"{A<T>} ... }};

11.11.2 片段和自动 typemap 特化

由于片段可以是类型专门化的,它们可以优雅地用于专门化类型映射。例如,如果你有类似的东西:

C++%fragment("incode"{float}, "header") { float in_method_float(PyObject *obj) { ... } } %fragment("incode"{long}, "header") { float in_method_long(PyObject *obj) { ... } } // %my_typemaps 宏定义%define %my_typemaps(Type) %typemap(in, fragment="incode"{Type}) Type { value = in_method_##Type(obj); } %enddef %my_typemaps(float); %my_typemaps(long);

然后将使用正确的“incode”{float}或“incode”{long} 片段,并且每当使用 float 或 long 类型作为输入参数时,将调用 in_method_float 和 in_method_long 方法。

此功能在某些脚本语言的 SWIG 库中提供的类型映射中得到了大量使用。感兴趣(或非常勇敢)的读者可以查看 SWIG 随附的 fragment.swg 文件,以了解其实际效果。

11.12 运行时类型检查器

大多数脚本语言在运行时需要类型信息。这个类型信息可以包括如何构造类型、如何垃圾收集类型以及类型之间的继承关系。如果语言接口没有提供自己的类型信息存储,生成的 SWIG 代码需要提供。

对类型系统的要求:

• 存储继承和类型等价信息,并能够正确地重新创建类型指针。

• 在模块之间共享类型信息。

• 模块可以以任何顺序加载,而不管实际的类型依赖。

• 一般避免使用动态分配的内存和库/系统调用。

• 提供相当快的实现,最大限度地减少所有语言模块的查找时间。

• 自定义的、特定于语言的信息可以附加到类型。

• 模块可以从类型系统中卸载。

11.12.1 实施

许多(但不是全部)SWIG 支持的目标语言都使用运行时类型检查器。运行时类型检查器功能不是必需的,因此不用于静态类型语言,例如 Java 和 C#。基于脚本和方案的语言依赖于它,它构成了 SWIG 对这些语言的操作的关键部分。

当指针、数组和对象被 SWIG 包装时,它们通常被转换为类型化指针对象。例如,Foo * 的一个实例可能是一个这样编码的字符串:

C++_108e688_p_Foo

在基本层面上,类型检查器只是为扩展模块恢复一些类型安全。但是,类型检查器还负责确保正确处理包装的 C++ 类——尤其是在使用继承时。当扩展模块使用多重继承时,这一点尤其重要。例如:

C++class Foo {public: int x;}; class Bar {public: int y;}; class FooBar : public Foo, public Bar {public: int z;};

当类FooBar在内存中组织时,它包含类Foo和Bar的内容以及它自己的数据成员。例如:

Plain TextFooBar --> | -----------| <-- Foo | int x | |------------| <-- Bar | int y | |------------| | int z | |------------|

由于基类数据堆叠在一起的方式,将 Foobar * 转换为任一基类可能会更改指针的实际值。这意味着使用简单的整数或空的 void * 来表示指针通常是不安全的 ——需要类型标签来实现对指针值的正确处理(并在需要时进行调整)。

在为每种语言生成的包装器代码中,通过使用特殊的类型描述符和转换函数来处理指针。例如,如果您查看 Python 的包装器代码,您将看到类似于以下的代码(为简洁起见进行了简化):

C++if (!SWIG_IsOK(SWIG_ConvertPtr(obj0, (void **) &arg1, SWIGTYPE_p_Foo, 0))) { SWIG_exception_fail(SWIG_TypeError, "in method 'GrabVal', expecting type Foo"); }

在此代码中,SWIGTYPE_p_Foo 是描述 Foo * 的类型描述符。类型描述符实际上是一个指向结构的指针,该结构包含有关在目标语言中使用的类型名称的信息、等效类型名称的列表(通过 typedef 或继承)和指针值处理信息(如果适用)。所述SWIG_ConvertPtr() 函数是一个简单的效用函数,它在目标语言和类型描述符对象的指针的对象,并使用该信息来生成C ++指针。该 SWIG_IsOK 错误和宏检查返回值SWIG_exception_fail 可以调用以在目标语言中引发异常。但是,转换函数的确切名称和调用约定取决于目标语言(有关详细信息,请参阅特定于语言的章节)。

实际的类型代码在 swigrun.swg 中,并插入到生成的 swig 包装文件的顶部附近。短语“可以转换为类型 Y 的类型 X”意味着给定类型 X,它可以转换为类型 Y。换句话说,X 是 Y 的派生类或 X 是 Y 的 typedef。存储类型信息的结构如下所示:

C++/* 存储一种类型信息的结构 */ typedef struct swig_type_info { const char *name; /* 这种类型的重整名称 */ const char *str; /* 这种类型的人类可读名称 */ swig_dycast_func dcast; /* 沿层次结构动态转换函数 */ struct swig_cast_info *cast; /* 可以转换为这种类型的类型的链接列表 */ void *clientdata; /* 语言特定类型数据 */ } swig_type_info; /* 用于存储类型和转换函数的结构*/ typedef struct swig_cast_info { swig_type_info *type; /* 指向与该类型等价的类型的指针 */ swig_converter_func converter; /* 转换空指针的函数 */ struct swig_cast_info *next; /* 指向链表中下一个转换的指针 */ struct swig_cast_info *prev; /* 指向前一个演员表的指针 */ } swig_cast_info;

每个 swig_type_info 存储一个它等价的类型的链表。这个双向链表中的每个条目都存储一个指向另一个 swig_type_info 结构的指针,以及一个指向转换函数的指针。这个转换函数用于解决 FooBar 类的上述问题,正确返回一个指向我们想要的类型的指针。

我们需要解决的基本问题是验证和构建传递给函数的参数。所以回到上面的 SWIG_ConvertPtr() 函数示例,我们期待一个 Foo * 并且需要检查obj0是否实际上是一个 Foo * 。以前,SWIGTYPE_p_Foo只是一个指向描述 Foo * 的 swig_type_info 结构的指针。因此,我们遍历附加到 SWIGTYPE_p_Foo的 swig_cast_info 结构的链表。如果我们看到 obj0 的类型在链表中,我们通过关联的转换函数传递对象,然后返回一个正数。如果我们在没有匹配的情况下到达链表的末尾,则 obj0 无法转换为 Foo *并产生错误。

另一个需要解决的问题是在多个模块之间共享类型信息。更明确地说,我们需要为每种类型设置一个 swig_type_info。如果两个模块都使用该类型,则加载的第二个模块必须从已加载的模块中查找并使用 swig_type_info 结构。由于没有使用动态内存,并且投射信息的循环依赖,加载类型信息有些棘手,这里不做解释。完整的描述在 Lib/swiginit.swg 文件中(靠近任何生成文件的顶部)。

每个模块都有一个 swig_module_info 结构,如下所示:

C++/* 用于存储模块信息的结构 * 每个模块生成一个这样的结构,运行时收集 * 所有这些结构并将它们存储在一个循环链表中。*/ typedef struct swig_module_info { swig_type_info **types; /* 指向模块中 swig_type_info 结构的指针数组 */ int size; /* 此模块中的类型数 */ struct swig_module_info *next; /* 指向循环链表中下一个元素的指针 */ swig_type_info **type_initial; /* 最初生成的类型结构数组 */ swig_cast_info **cast_initial; /* 最初生成的铸造结构数组 */ void *clientdata; /* 语言特定的模块数据 */ } swig_module_info;

每个模块存储一个指向 swig_type_info 结构和该模块中类型数量的指针数组。因此,当加载第二个模块时,它会找到第一个模块的 swig_module_info 结构并搜索类型数组。如果它自己的任何类型在第一个模块中并且已经被加载,它使用那些 swig_type_inf o结构而不是创建新的。这些 swig_module_info 结构在循环链表中链接在一起。

11.12.2 用法

本节介绍如何使用类型映射中的这些函数。要了解如何从外部文件(不是生成的 _wrap.c 文件)调用这些函数,请参阅对运行时系统的外部访问部分。

在 typemap 中转换指针时,typemap 代码通常类似于以下内容:

C++%typemap(in) Foo * { if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, 0))) { SWIG_exception_fail(SWIG_TypeError, "in method '$symname', expecting type Foo" ); } }

最关键的部分是 typemap 是使用了 1_descriptor 特殊变量。当放置在类型映射中时,它会扩展为上面的 SWIGTYPE_* 类型描述符对象。作为一般规则,您应该始终使用 1_descripto r而不是尝试直接对类型描述符名称进行硬编码。

您应该始终使用 $1_descriptor 变量还有另一个原因。当这个特殊变量被扩展时,SWIG 将相应的类型标记为“正在使用”。当类型表和类型信息在包装器文件中发出时,仅为接口中实际使用的那些数据类型生成描述符信息。这大大减少了类型表的大小并提高了效率。

有时,您可能需要编写需要转换其他类型指针的类型映射。为了解决这个问题,前面介绍的特殊变量宏 $descriptor(type) 可用于为任何 C 数据类型生成 SWIG 类型描述符名称。例如:

C++%typemap(in) Foo * { if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, 0))) { Bar *temp; if (!SWIG_IsOK(SWIG_ConvertPtr($input, (void **) &temp, $descriptor(Bar *), 0))) { SWIG_exception_fail(SWIG_TypeError, "in method '$symname', expecting type Foo or Bar"); } $1 = (Foo *)temp; }}

$descriptor(type) 的主要用途是为容器对象和其他复杂数据结构编写类型映射。参数有一些限制——即它必须是完全定义的 C 数据类型。它不能是任何特殊的类型映射变量。

在某些情况下,SWIG 可能不会像您期望的那样生成类型描述符。例如,如果您以某种非标准方式转换指针或使用接口文件和模块的不寻常组合,您可能会发现 SWIG 忽略了特定类型描述符的信息。要解决此问题,您可能需要使用 %types 指令。例如:

C++%types(int *, short *, long *, float *, double *);

使用 %types 时,即使这些数据类型从未出现在接口文件的其他地方,SWIG 也会生成类型描述符信息。

有关运行时类型检查的更多详细信息,请参见各个语言模块的文档。阅读源代码也可能有所帮助。SWIG 库中的 Lib/swigrun.swg 文件包含生成的所有类型检查代码的源代码。此代码也包含在每个生成的包装文件中,因此您可能只需查看 SWIG 的输出即可更好地了解如何管理类型。

11.13 Typemaps 和重载

本节不适用于 Java 和 C# 等静态类型语言,在这些语言中,通过在目标语言中生成重载方法,处理类型的重载与 C++ 非常相似。在许多其他目标语言中,SWIG 仍然完全支持 C++ 重载方法和函数。例如,如果您有一个这样的函数集合:

C++int foo(int x); int foo(double x); int foo(char *s, int y);

您可以从脚本解释器以正常方式访问这些函数:

Python# Python foo(3) # foo(int) foo(3.5) # foo(double) foo("hello", 5) # foo(char *, int) # Tcl foo 3 # foo(int) foo 3.5 # foo( double) foo hello 5 # foo(char *, int)

为了实现重载,SWIG 为每个重载的方法生成一个单独的包装函数。例如,上述函数将产生大致如下所示的内容:

C++// wrapper pseudocode_wrap_foo_0(argc, args[]) { // foo(int) int arg1; int result; ... arg1 = FromInteger(args[0]); result = foo(arg1); return ToInteger(result);} _wrap_foo_1(argc, args[]) { // foo(double) double arg1; int result; ... arg1 = FromDouble(args[0]); result = foo(arg1); return ToInteger(result);} _wrap_foo_2(argc, args[]) { // foo(char *, int) char *arg1; int arg2; int result; ... arg1 = FromString(args[0]); arg2 = FromInteger(args[1]); result = foo(arg1, arg2); return ToInteger(result);}

接下来,生成一个动态调度函数:

JavaScript_wrap_foo(argc, args[]) { if (argc == 1) { if (IsInteger(args[0])) { return _wrap_foo_0(argc, args); } if (IsDouble(args[0])) { return _wrap_foo_1(argc, args); } } if (argc == 2) { if (IsString(args[0]) && IsInteger(args[1])) { return _wrap_foo_2(argc, args); } } error("No matching function!\n"); }

动态调度函数的目的是根据参数类型选择合适的 C++ 函数——这是大多数 SWIG 目标语言在运行时必须执行的任务。

动态调度函数的生成是一件相对棘手的事情。不仅必须考虑输入类型映射(这些类型映射可以从根本上改变接受的参数类型),而且还必须以非常特定的顺序对重载的方法进行排序和检查,以解决潜在的歧义。在“ SWIG 和 C++ ”一章中可以找到此排名过程的高级概述。在那一章中没有提到的是它的实现机制——作为类型映射的集合。

为了支持动态调度,SWIG 首先定义了一个通用类型层次结构,如下所示:

C++Symbolic Name Precedence Value------------------------------ ------------------SWIG_TYPECHECK_POINTER 0 SWIG_TYPECHECK_VOIDPTR 10 SWIG_TYPECHECK_BOOL 15 SWIG_TYPECHECK_UINT8 20 SWIG_TYPECHECK_INT8 25 SWIG_TYPECHECK_UINT16 30 SWIG_TYPECHECK_INT16 35 SWIG_TYPECHECK_UINT32 40 SWIG_TYPECHECK_INT32 45 SWIG_TYPECHECK_UINT64 50 SWIG_TYPECHECK_INT64 55 SWIG_TYPECHECK_UINT128 60 SWIG_TYPECHECK_INT128 65 SWIG_TYPECHECK_INTEGER 70 SWIG_TYPECHECK_FLOAT 80 SWIG_TYPECHECK_DOUBLE 90 SWIG_TYPECHECK_COMPLEX 100 SWIG_TYPECHECK_UNICHAR 110 SWIG_TYPECHECK_UNISTRING 120 SWIG_TYPECHECK_CHAR 130 SWIG_TYPECHECK_STRING 140 SWIG_TYPECHECK_BOOL_ARRAY 1015 SWIG_TYPECHECK_INT8_ARRAY 1025 SWIG_TYPECHECK_INT16_ARRAY 1035 SWIG_TYPECHECK_INT32_ARRAY 1045 SWIG_TYPECHECK_INT64_ARRAY 1055 SWIG_TYPECHECK_INT128_ARRAY 1065 SWIG_TYPECHECK_FLOAT_ARRAY 1080 SWIG_TYPECHECK_DOUBLE_ARRAY 1090 SWIG_TYPECHECK_CHAR_ARRAY 1130 SWIG_TYPECHECK_STRING_ARRAY 1140

(这些优先级在 swig.swg 中定义,这是一个包含在所有目标语言模块中的库文件。)

在这个表中,优先级决定了检查类型的顺序。低值总是在高值之前检查。例如,在浮点数之前检查整数,在数组之前检查单个值,等等。

使用上表作为指导,每种目标语言都定义了一组“类型检查”类型映射。Python 模块的以下摘录说明了这一点:

Objective-C/* Python 类型检查规则 */ /* 注意:%typecheck(X) 是 %typemap(typecheck, precedence=X) 的宏 */ %typecheck(SWIG_TYPECHECK_INTEGER) int, short, long, unsigned int, unsigned short, unsigned long, signed char, unsigned char, long long, unsigned long long, const int &,const short &,const long &, const unsigned int &,const unsigned short &,const unsigned long &, const long long &,const unsigned long long &, enum SWIGTYPE, bool, const bool & { $1 = (PyInt_Check($input) || PyLong_Check($input)) ? 1:0;} %typecheck(SWIG_TYPECHECK_DOUBLE) float, double, const float &,{ $1 = (PyFloat_Check($input) || PyInt_Check($input) || PyLong_Check($input)) ? 1:0;} %typecheck(SWIG_TYPECHECK_CHAR) char { $1 = (PyString_Check($input) && (PyString_Size($input) == 1)) ? 1:0;} %typecheck(SWIG_TYPECHECK_STRING) char * { $1 = PyString_Check($input) ? 1:0;} %typemap(typecheck, precedence=SWIG_TYPECHECK_POINTER, noblock=1) SWIGTYPE * { void *vptr = 0; int res = SWIG_ConvertPtr($input, &vptr, $1_descriptor, 0); $1 = SWIG_IsOK(res) ? 1:0;} %typecheck(SWIG_TYPECHECK_POINTER) PyObject * { $1 = ($input != 0); }

可能需要考虑一下,但这段代码仅组织了所有基本的 C++ 类型,提供了一些简单的类型检查代码,并为每个类型分配了一个优先级值。

最后,为了生成动态调度函数,SWIG 使用以下算法:

• 重载方法首先按所需参数的数量排序。

• 具有相同数量参数的方法然后按参数类型的优先级值排序。

• 然后发出类型检查类型映射以生成一个调度函数,该函数以正确的顺序检查参数。

如果您还没有自己编写任何类型映射,则无需担心类型检查规则。但是,如果您编写了新的输入类型映射,则可能还必须提供类型检查规则。一种简单的方法是简单地复制现有的类型检查规则之一。这是一个例子,

C++// C++ 字符串的类型映射%typemap(in) std::string { if (PyString_Check($input)) { $1 = std::string(PyString_AsString($input)); } else { SWIG_exception(SWIG_TypeError, "string expected"); } } // 复制“char *”的类型检查代码。 %typemap(typecheck) std::string = char *;

底线:如果您正在编写新的类型映射并使用重载方法,您可能必须编写新的类型检查代码或复制和修改现有的类型检查代码。

如果你写了一个 typecheck typemap 并省略了优先级,例如将其注释掉,如下所示:

C++%typemap(typecheck /*, precedence=SWIG_TYPECHECK_INTEGER*/) int { $1 = PyInt_Check($input) ? 1:0;}

然后该类型的优先级高于任何其他已知的优先级,并发出警告:

Plain Textexample.i:18: Warning 467: Overloaded method foo(int) not supported (incomplete type checking rule - no precedence level in typecheck typemap for 'int').

笔记:

• 类型检查类型映射不用于非重载方法。因此,始终需要检查任何“in”类型映射中的类型。

• 动态调度过程只是一种启发式方法。在许多极端情况下,SWIG 根本无法像 C++ 那样消除类型的歧义。解决这种歧义的唯一方法是使用 %rename 指令重命名重载方法之一(有效地消除重载)。

• 类型检查可能是部分的。例如,如果使用数组,类型检查代码可能只检查第一个数组元素的类型并使用它来分派到正确的函数。随后的“in”类型映射将执行更广泛的类型检查。

• 请务必阅读“ SWIG 和 C++ ”一章中有关重载的部分。

11.14 更多关于 %apply 和 %clear

为了实现某些类型的程序行为,有时需要编写类型映射集。例如,为了支持输出参数,人们经常编写一组这样的类型映射:

C++%typemap(in, numinputs=0) int *OUTPUT (int temp) { $1 = &temp; } %typemap(argout) int *OUTPUT { // 以某种方式返回值}

为了更容易地将类型映射应用于不同的参数类型和名称,%apply指令将所有类型映射从一种类型复制到另一种类型。例如,如果你指定这个,

C++%apply int *OUTPUT { int *retvalue, int32 *output };

然后将所有 int *OUTPUT 类型映射复制到 int *retvalue 和 int32 *output。

然而,%apply 有一个需要更多描述的微妙方面。也就是说,如果已经为目标数据类型定义了类型映射规则,则 %apply 不会覆盖它。这种行为允许你做两件事:

• 您可以通过首先定义一些类型映射然后使用 %apply 合并其余部分来专门化复杂类型映射规则的部分。

• 可以使用重复的 %apply 指令将一组不同的类型映射应用于相同的数据类型。

例如:

C++%typemap(in) int *INPUT (int temp) { temp = ... get value from $input ...; $1 = &temp; } %typemap(check) int *POSITIVE { if (*$1 <= 0) { SWIG_exception(SWIG_ValueError, "Expected a positive number!\n"); return NULL; }} ...%apply int *INPUT { int *invalue };%apply int *POSITIVE { int *invalue };

由于 %apply 不会覆盖或替换任何现有规则,因此重置行为的唯一方法是使用 %clear 指令。%clear 删除为特定数据类型定义的所有类型映射规则。例如:

C++%clear int *invalue;

11.15 在 typemaps 之间传递数据

同样重要的是要注意,局部变量的主要用途是创建堆栈分配的对象,以便在包装函数内临时使用(这比在堆上分配数据更快且不易出错)。通常,变量不打算在不同类型的类型映射之间传递信息。但是,如果您意识到本地名称附加了参数编号,则可以这样做。例如,你可以这样做:

C++%typemap(in) int *(int temp) { temp = (int) PyInt_AsLong($input); $1 = &temp; } %typemap(argout) int * { PyObject *o = PyInt_FromLong(temp$argnum); ... }

在这种情况下,$argnum变量被扩展为参数编号。因此,代码将引用适当的本地,例如 temp1 和 temp2。应该注意的是,这里有很多机会打破宇宙,应该避免以这种方式访问本地人。至少,您应该确保共享信息的类型映射具有完全相同的类型和名称。

11.16 C++“this”指针

所有 typemaps 讨论的规则适用于 C ++ 以及 C。然而除了 C ++ 通过一个额外的参数到每个非静态类方法-在此指针。有时,将类型映射应用于此指针可能很有用(例如,在延迟之前检查并确保它为非空)。实际上,C 也有一个等效的 this 指针,它在访问 C 结构中的变量时使用。

为了自定义 this 指针处理,在你的类型映射中定位一个名为 self 的变量。self 是 SWIG 用来指代包装函数中的额外参数的名称。

例如,如果为 Java 生成包装:

Perl%typemap(check) SWIGTYPE *self %{if (!$1) { SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "invalid native object; delete() likely already called"); return $null;}%}

在上述情况下,$1 变量被扩展为 SWIG 用作 this 指针的参数名称。然后,SWIG 将在调用实际 C++ 类方法之前插入检查代码,并引发异常而不是使 Java 虚拟机崩溃。生成的代码将类似于:

C++ if (!arg1) { SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "invalid native object; delete() likely already called"); return ; } (arg1)->wrappedFunction(...);

请注意,如果您有一个名为 self 的参数,那么它也将匹配类型映射。一种解决方法是创建一个包装方法的接口文件,但为参数指定一个不同于 self 的名称。

11.17 去哪里了解更多信息?

查找有关编写类型映射的更多信息的最佳位置是查看 SWIG 库。大多数语言模块使用类型映射定义它们的所有默认行为。这些可以在 python.swg、perl5.swg、tcl8.swg 等文件中找到。库中的 typemaps.i 文件还包含许多示例。您应该查看这些文件以了解如何定义您自己的类型映射。一些语言模块支持额外的类型映射,并且在每种目标语言的各个章节中提供了更多信息。在那里您还可以找到更多动手实践的例子。

12 自定义功能

在许多情况下,需要更改接口中特定声明的默认包装。例如,您可能希望提供用于捕获 C++ 异常、添加断言或向底层代码生成器提供提示的钩子。本章介绍了其中一些自定义技术。首先,讨论异常处理。然后,描述了称为“features”的更通用的定制机制。

12.1 %exception 的异常处理

在%exception 指令允许你定义一个通用的异常处理程序。例如,您可以指定以下内容:

C++%exception { try { $action } catch (RangeError) { ... 处理错误 ... } }

如何处理异常取决于目标语言,例如 Python:

C++%exception { try { $action } catch (RangeError) { PyErr_SetString(PyExc_IndexError, "index out-of-bounds"); SWIG_fail; } }

定义后,括在大括号中的代码直接插入低级包装函数中。特殊变量 $action 是少数受支持的 %exception 特殊变量之一,并被要执行的实际操作(函数调用、方法调用、属性访问等)替换。异常处理程序在被显式删除之前一直有效。这是通过使用没有代码的 %exception 或 %noexception 来完成的。例如:

C++%exception; // 删除任何先前定义的处理程序

兼容性说明:以前版本的 SWIG 使用特殊指令 %except 进行异常处理。该指令已被弃用—— %exception 提供了相同的功能,但更加灵活。

12.1.1 C 代码中的异常处理

C 没有正式的异常处理机制,因此可以使用多种方法。一种比较常见的技术是简单地设置一个特殊的错误代码。例如:

C++/* File : except.c */ static char error_message[256];static int error_status = 0; void throw_exception(char *msg) { strncpy(error_message, msg, 256); error_status = 1;} void clear_exception() { error_status = 0;}char *check_exception() { if (error_status) return error_message; else return NULL;}

要使用这些函数,函数只需调用 throw_exception() 来指示发生了错误。例如:

C++double inv(double x) { if (x != 0) return 1.0/x; else { throw_exception("Division by zero"); return 0; }}

要捕获异常,您可以编写一个简单的异常处理程序,如下所示(针对 Perl5 显示):

C++%exception { char *err; clear_exception(); $action if ((err = check_exception())) { croak(err); }}

在这种情况下,当发生错误时,它会被转换为 Perl 错误。每个目标语言都有自己的方法来在 Perl 中创建运行时错误/异常,它是上面显示的 croak 方法。

12.1.2 使用 longjmp() 处理异常

也可以使用 <setjmp.h>库将异常处理添加到 C 代码中。这是一个依赖于 C 预处理器的简约实现:

C++/* File : except.c Just the declaration of a few global variables we're going to use */ #include <setjmp.h>jmp_buf exception_buffer;int exception_status; /* File : except.h */#include <setjmp.h>extern jmp_buf exception_buffer;extern int exception_status; #define try if ((exception_status = setjmp(exception_buffer)) == 0)#define catch(val) else if (exception_status == val)#define throw(val) longjmp(exception_buffer, val)#define finally else /* Exception codes */ #define RangeError 1#define DivisionByZero 2#define OutOfMemory 3

现在,在 C 程序中,您可以执行以下操作:

C++double inv(double x) { if (x) return 1.0/x; else throw(DivisionByZero);}

最后,要创建 SWIG 异常处理程序,请编写以下内容:

C++%{#include "except.h"%} %exception { try { $action } catch(RangeError) { croak("Range Error"); } catch(DivisionByZero) { croak("Division by zero"); } catch(OutOfMemory) { croak("Out of memory"); } finally { croak("Unknown exception"); }}

注意:此实现仅用于说明一般思想。为了让它更好地工作,您需要修改它以处理嵌套的 try声明。

12.1.3 处理 C++ 异常

处理 C++ 异常也很简单。例如:

C++%exception { try { $action } catch(RangeError) { croak("Range Error"); } catch(DivisionByZero) { croak("Division by zero"); } catch(OutOfMemory) { croak("Out of memory"); } catch(...) { croak("Unknown exception"); }}

异常类型需要在别处声明为类,可能在头文件中:

C++class RangeError {};class DivisionByZero {};class OutOfMemory {};

12.1.4 变量的异常处理程序

默认情况下,所有变量都将忽略 %exception,因此对于所有变量包装器,它被有效地关闭。这适用于全局变量、成员变量和静态成员变量。在 C 中包装变量时,这种方法当然是一种合乎逻辑的方法。但是,在 C++ 中,很可能在分配变量时抛出异常。为确保在包装变量时使用 %exception,需要使用 %allowexception 功能“打开”它。请注意,%allowexception只是 %feature("allowexcept") 的宏,也就是说,它是一个名为 “allowexcept” 的功能。任何附加了此功能的变量都将使用 %exception 功能,但当然,前提是首先有一个附加到变量的 %exception。在%allowexception 功能就像任何其他的功能,因此可以在全局或选择性的变量中。

C++%allowexception; // turn on globally%allowexception Klass::MyVar; // turn on for a specific variable %noallowexception Klass::MyVar; // turn off for a specific variable%noallowexception; // turn off globally

12.1.5 定义不同的异常处理程序

默认情况下,%exception 指令创建一个异常处理程序,用于所有跟随它的包装函数。除非有一个定义良好(且简单)的错误处理机制,否则定义一个通用异常处理程序可能很笨拙并导致过度的代码膨胀,因为处理程序被内联到每个包装器函数中。

要解决此问题,您可以更有选择性地使用 %exception指令。一种方法是仅将其放置在关键代码段周围。例如:

C++%exception { ... 你的异常处理程序 ... } /* 定义可以在这里抛出异常的关键操作 */ %exception; /* 定义不抛出异常的非关键操作 */

通过将异常处理程序附加到特定的声明名称,可以获得对异常处理的更精确控制。例如:

C++%exception allocate { try { $action } catch (MemoryError) { croak("Out of memory"); }}

在这种情况下,异常处理程序仅附加到名为“allocate”的声明。这将包括全局和成员函数。提供给%exception的名称遵循与歧义解析和重命名部分中描述的 %rename 相同的规则。例如,如果你想为一个特定的类定义一个异常处理程序,你可以这样写:

C++%exception Object::allocate { try { $action } catch (MemoryError) { croak("Out of memory"); } }

当提供类前缀时,异常处理程序将应用于指定类中的相应声明以及派生类中出现的同名函数。

%exceptio n甚至可用于在使用重载时查明精确声明。例如:

C++%exception Object::allocate(int) { try { $action } catch (MemoryError) { croak("Out of memory"); } }

将异常附加到特定声明是减少代码膨胀的好方法。将异常附加到头文件的特定部分也是一种有用的方法。例如:

PHP%module example %{ #include "someheader.h" %} // 为特定声明定义一些异常处理程序%exception Object::allocate(int) { try { $action } catch (MemoryError) { croak("Out of memory "); } } %exception Object::getitem { try { $action } catch (RangeError) { croak("Index out of range"); } } ... // 读取原始头文件%include "someheader.h"

兼容性注意:在%exception 指令替换由“除”类型映射弃用的所提供的功能。typemap 将允许基于函数的返回类型在目标语言中抛出异常,并且旨在成为一种用于精确定位特定声明的机制。然而,它从来没有真正有效地工作过,新的 %exception 指令要好得多。

12.1.6 %exception 的特殊变量

%exception 指令支持一些特殊变量,它们是代码替换的占位符。下表显示了可用的特殊变量并详细说明了特殊变量被替换的内容。

$action

要执行的实际操作(函数调用、方法调用、变量访问等)

$name

函数的 C/C++ 符号名称。

$symname

SWIG 内部使用的符号名称

$overname

重载方法的符号名称中使用的额外重整。如果包装的方法没有重载,则扩展为空。

$wrapname

语言特定的包装器名称(通常是从共享对象/dll 导出的 C 函数名称)

$decl

被包装的方法的完全限定 C/C++ 声明,没有返回类型

$fulldecl

被包装方法的完全限定 C/C++ 声明,包括返回类型

$parentclassname

方法的父类名称(如果有)。

$parentclasssymname

方法的目标语言父类名称(如果有)。

特殊变量通常用于记录方法调用的情况。究竟哪种形式的方法调用需要记录取决于个人要求,但下面的示例代码显示了所有可能的扩展,以及如何定制异常消息以显示 C++ 方法声明:

C++%exception Special::something { log("symname: $symname"); log("overname: $overname"); log("wrapname: $wrapname"); log("decl: $decl"); log("fulldecl: $fulldecl"); try { $action } catch (MemoryError) { croak("Out of memory in $decl"); }}void log(const char *message);struct Special { void something(const char *c); void something(int i);};

下面显示了Perl的第一个重载 something 包装器方法的扩展:

Perl log("symname: Special_something"); log("overname: __SWIG_0"); log("wrapname: _wrap_Special_something__SWIG_0"); log("decl: Special::something(char const *)"); log("fulldecl: void Special::something(char const *)"); try { (arg1)->something((char const *)arg2); } catch (MemoryError) { croak("Out of memory in Special::something(char const *)"); }

12.1.7 使用 SWIG 异常库

该 exception.i 库文件提供了你的接口建立与语言无关的异常的支持。要使用它,只需在您的接口文件中放置一个“ %include exception.i ”。这提供了一个函数 SWIG_exception(),可用于以可移植的方式引发常见的脚本语言异常。例如:

C++// 语言无关的异常处理程序%include exception.i %exception { try { $action } catch(RangeError) { SWIG_exception(SWIG_ValueError, "Range Error"); } catch(DivisionByZero) { SWIG_exception(SWIG_DivisionByZero, "除以零"); } catch(OutOfMemory) { SWIG_exception(SWIG_MemoryError, "内存不足"); } catch(...) { SWIG_exception(SWIG_RuntimeError, "未知异常"); } }

作为参数,SWIG_exception() 采用错误类型代码(整数)和错误消息字符串。当前支持的错误类型有:

C++SWIG_UnknownError SWIG_IOError SWIG_RuntimeError SWIG_IndexError SWIG_TypeError SWIG_DivisionByZero SWIG_OverflowError SWIG_SyntaxError SWIG_ValueError SWIG_SystemError SWIG_AttributeError SWIG_MemoryError SWIG_NullReferenceError

所述 SWIG_exception()函数也可以在 typemaps 使用。

12.2 对象所有权和 %newobject

某些应用程序中的一个常见问题是管理对象的正确所有权。例如,考虑这样的函数:

C++Foo *blah() { Foo *f = new Foo(); return f;}

如果包装函数 blah(),SWIG 不知道返回值是新分配的对象。因此,生成的扩展模块可能会产生内存泄漏(SWIG 是保守的并且永远不会删除对象,除非它确定返回的对象是新创建的)。

要解决此问题,您可以使用 %newobject 指令向代码生成器提供额外提示。例如:

C++%newobject blah;Foo *blah();

%newobject 的工作原理与 %rename 和 %exception完全一样。换句话说,您可以像以前一样将其附加到类成员和参数化声明。例如:

C++%newobject::blah(); // 仅适用于全局 blah %newobject Object::blah(int, double); // 只有 blah(int, double) in Object %newobject *::copy; // 在所有类中复制方法...

当提供 %newobject 时,许多语言模块将安排获取返回值的所有权。这允许该值在不再使用时自动进行垃圾收集。然而,这完全取决于目标语言(语言模块也可以选择忽略 %newobject 指令)。

与 %newobject 密切相关的是一个特殊的类型映射。“newfree”类型映射可用于释放新分配的返回值。它仅适用于已应用%newobject 的方法,并且通常用于清理字符串结果。例如:

C++%typemap(newfree) char * "free($1);"; ... %newobject strdup; ... char *strdup(const char *s);

在这种情况下,函数的结果是目标语言中的字符串。由于此字符串是原始结果的副本,因此不再需要 strdup() 返回的数据。示例中的“newfree”类型映射只是释放此内存。

作为%newobject的补充,从 SWIG 1.3.28 开始,您可以使用%delobject指令。例如,如果您有两种方法,一种用于创建对象,另一种用于销毁它们,则可以使用:

C++%newobject create_foo; %delobject destroy_foo; ... Foo *create_foo(); void destroy_foo(Foo *foo);

或在成员方法中:

C++%delobject Foo::destroy; class Foo { public: void destroy() { delete this;} private: ~Foo(); };

%delobject 指示 SWIG 传递给该方法的第一个参数将被销毁,因此,目标语言不应尝试将其释放两次。这类似于在第一个方法参数中使用 DISOWN 类型映射,实际上,它也取决于目标语言是否正确实现了“disown”机制。

%newobject 的使用也与引用计数集成在一起,并在C++ 引用计数对象部分进行了介绍。

兼容性说明:以前版本的 SWIG 有一个特殊的 %new 指令。但是,与%newobject 不同,它只适用于下一个声明。例如:

C++%new char *strdup(const char *s);

目前,这仍然受支持,但已弃用。

如何自找麻烦:将%newobject 指令不声明修饰符像老%new 指令。不要写这样的代码:

C++%newobject char *strdup(const char *s);

结果可能不是您所期望的。

12.3 特性和 %feature 指令

既%except 和%newobject 是被称为更通用的定制机制的示例的“特征”。功能只是附加到特定声明的用户可定义属性。使用 %feature 指令附加功能。例如:

C++%feature("except") Object::allocate { try { $action } catch (MemoryError) { croak("Out of memory"); } } %feature("new", "1") *::copy;

事实上,%exception 和 %newobject 指令实际上只不过是涉及 %feature 的宏:

C++#define %exception %feature("except") #define %newobject %feature("new", "1")

歧义解析和重命名部分中概述的名称匹配规则适用于所有 %feature 指令。事实上 %rename 指令只是%feature 的一种特殊形式。匹配规则意味着功能非常灵活,如果需要,可以精确地应用于特定声明。此外,如果未给出声明名称,则称定义了全局特性。然后将此功能附加到随后的每个声明中。这就是定义全局异常处理程序的方式。例如:

C++/* 定义一个全局异常处理程序 */ %feature("except") { try { $action } ... } ... 一堆声明 ...

在%功能指令可以用不同的语法来使用。以下都是等价的:

C++%feature("except") Object::method { $action }; %feature("except") Object::method %{ $action %}; %feature("except") Object::method " $action "; %feature("except", "$action") Object::method;

第一个变体中的语法将生成使用的{ } 分隔符,而其他变体则不会。

12.3.1 Feature flags

%feature 指令还以与 typemap 相同的方式接受 XML 样式属性。可以指定任意数量的属性。以下是功能的通用语法:

C++%feature("name", "value", attribute1="AttributeValue1") symbol;%feature("name", attribute1="AttributeValue1") symbol {value};%feature("name", attribute1="AttributeValue1") symbol %{value%};%feature("name", attribute1="AttributeValue1") symbol "value";

可以使用逗号分隔列表指定多个属性。Java 模块是一个使用 %feature("except") 中的属性的示例。在抛出属性指定的Java类的名称添加到代理方法的throws子句。在以下示例中,MyExceptionClass 是用于添加到 throws 子句的 Java 类的名称。

C++%feature("except", throws="MyExceptionClass") Object::method { try { $action } catch (...) { ... code to throw a MyExceptionClass Java exception ... }};

可以从 Java 异常处理部分获得更多详细信息。

12.3.2 功能标志

功能标志用于启用或禁用特定功能。功能标志是 %feature 的一个常见但简单的用法,功能值应该是1启用或 0 禁用该功能。

C++%feature("featurename") // 启用特性%feature("featurename", "1") // 启用特性%feature("featurename", "x") // 启用特性%feature("featurename", "0 ") // 禁用功能%feature("featurename", "") // 清除功能

实际上,除零以外的任何值都将启用该功能。请注意,如果完全省略该值,则默认值变为 1,从而启用该功能。通过不指定任何值来清除特征,请参阅清除特征。创建只读变量部分中描述的 %immutable 指令只是 %feature("immutable") 的宏,可用于演示功能标志:

C++ // 默认情况下禁用功能int red; // 可变 %feature("immutable"); // 全局启用int orange; // 不可变 %feature("immutable", "0"); // 全局禁用int 黄色; // 可变 %feature("immutable", "1"); // 另一种形式的 global enable int green; // 不可变 %feature("immutable", ""); // 清除全局特征int blue; // 可变

请注意,默认情况下禁用功能,必须在全局或通过指定目标声明显式启用。上面用 C 代码穿插了 SWIG 指令。当然,您可以明确地针对特征,因此上述内容也可以重写为:

C++%feature("immutable", "1") orange;%feature("immutable", "1") green;int red; // mutableint orange; // immutableint yellow; // mutableint green; // immutableint blue; // mutable

当从 C 头文件解析 C 声明时,上述方法允许将 C 声明与 SWIG 指令分开。上面的逻辑当然可以颠倒并重写为:

C++%feature("immutable", "1");%feature("immutable", "0") red;%feature("immutable", "0") yellow;%feature("immutable", "0") blue;int red; // mutableint orange; // immutableint yellow; // mutableint green; // immutableint blue; // mutable

正如上面 %immutable 所暗示的,大多数功能标志也可以通过替代语法指定。替代语法只是 swig.swg库文件中的一个宏。下面显示了虚构的 featurename 功能的替代语法:

C++%featurename // 等效于 %feature("featurename", "1") 即启用功能%nofeaturename // 等效于 %feature("featurename", "0") 即禁用功能%clearfeaturename // 等效于 %feature("功能名称", "") 即清除功能

接下来讨论清除特征的概念。

12.3.3 清除特征

一项功能将一直有效,直到它被明确清除。通过提供一个没有值的 %feature 指令来清除一个特性。例如 %feature("name", "")。清除的特征意味着在名称匹配规则中不再使用与任何先前定义的特征完全匹配的任何特征。因此,如果某个功能被清除,则可能意味着将应用另一个名称匹配规则。为了澄清,让我们再次考虑 except 功能(%exception):

C++// Define global exception handler%feature("except") { try { $action } catch (...) { croak("Unknown C++ exception"); }} // Define exception handler for all clone methods to log the method calls%feature("except") *::clone() { try { logger.info("$action"); $action } catch (...) { croak("Unknown C++ exception"); }} ... initial set of class declarations with clone methods ... // clear the previously defined feature%feature("except", "") *::clone(); ... final set of class declarations with clone methods ...

在上述场景中,最初的一组克隆方法将记录来自目标语言的所有方法调用。为最后一组克隆方法清除了此特定功能。但是,这些克隆方法仍将有一个异常处理程序(没有日志记录),因为与它们匹配的下一个最佳功能是全局异常处理程序。

请注意,清除功能并不总是与禁用功能相同。使用 %feature("except", "")*::clone() 清除上述功能与指定 %feature("except", "0")*::clone() 不同。前者将禁用克隆方法的功能 - 该功能仍然比全局功能更好。另一方面,如果根本没有定义全局异常处理程序,那么清除该功能将与禁用它相同,因为没有其他功能匹配。

请注意,该特征必须完全匹配才能被任何先前定义的特征清除。例如,以下尝试清除初始功能将不起作用:

C++%feature("except") clone() { logger.info("$action"); $action } %feature("except", "") *::clone();

但这将:

C++%feature("except") clone() { logger.info("$action"); $action } %feature("except", "") clone();

SWIG 提供了用于禁用和清除功能的宏。其中许多可以在 swig.swg 库文件中找到。典型的模式是定义三个宏;一种用于定义功能本身,一种用于禁用该功能,另一种用于清除该功能。下面的三个宏显示了“except”功能的这一点:

C++#define %exception %feature("except") #define %noexception %feature("except", "0") #define %clearexception %feature("except", "")

12.3.4 特性和默认参数

SWIG 将具有默认参数的方法视为单独的重载方法,如默认参数部分所述。如果在功能中指定了默认参数,则任何针对具有默认参数的方法的 %feature 都将应用于 SWIG 生成的所有额外重载方法。如果功能中未指定默认参数,则该功能将仅匹配该确切的包装器方法,而不匹配 SWIG 生成的额外重载方法。例如:

C++%feature("except") hello(int i=0, double d=0.0) { ... } void hello(int i=0, double d=0.0);

将该功能应用于所有三种包装方法,即:

C++void hello(int i, double d);void hello(int i);void hello();

如果功能中未指定默认参数:

C++%feature("except") hello(int i, double d) { ... } void hello(int i=0, double d=0.0);

那么该功能将仅适用于此包装器方法:

C++void hello(int i, double d);

而不是这些包装方法:

C++void hello(int i);void hello();

如果使用了compactdefaultargs,则在特性中指定或不指定默认参数之间的区别不适用,因为只生成一个包装器。

兼容性说明:当使用默认参数包装方法的方法发生变化时,SWIG-1.3.23 中引入了使用或不使用默认参数指定的功能的不同行为。

12.3.5 特征示例

如前所述,%feature 指令的预期用途是作为一种高度灵活的自定义机制,可用于用附加信息注释声明,以供特定目标语言模块使用。另一个例子是在 Python 模块中。您可以使用 %feature 重写代理/影子类代码,如下所示:

C++%module example%rename(bar_id) bar(int, double); // Rewrite bar() to allow some nice overloading %feature("shadow") Foo::bar(int) %{def bar(*args): if len(args) == 3: return apply(examplec.Foo_bar_id, args) return apply(examplec.Foo_bar, args)%} class Foo {public: int bar(int x); int bar(int x, double y);}

特定语言模块的文档中描述了 %feature 用法的更多详细信息。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档