文章目录
- 一、什么是Typemap?
- 二、如何定义个Typemap
- 三、Typemap都可以用来做什么?
- 3.1 入参映射处理
- 3.2 函数返回值映射处理
- 3.3 异常处理
- 3.4 全局变量处理
- 3.5 其它
- 3.6 哪些做不到?
- 四、typemap的作用域与删除
- 五、Typemap匹配规则
- 六、代码生成规则
- 七、内置的一些特殊变量
- 八、常用的typemap方法
- 8.1 in 方法
- 8.2 out 方法
- 8.3 argout 方法
- 8.4 freearg 方法
- 8.5 ret 方法
一、什么是Typemap?
Typemap时一种可以让你控制SWIG底层生成逻辑的高级自定义特性。SWIG的使用者一般都绕不开它。
如下的C++代码:
class Spam
{
public:
void changeFoo(Foo*& f);
};
当我们使用C#进行调用时,我们期望的调用方式如下:
var s = new Spam();
var f = new Foo();
s.changeFoo(ref f);
但是Foo*&
却被包装为了SWIGTYPE_p_p_Foo
这种我们无法直接使用的类型:
Typemap的出现就是为了解决这种实际类型与期望类型不一致的问题。
二、如何定义个Typemap
使用%typemap
指令来定义Typemap。指令格式如下:
%typemap(method [, modifiers]) typelist code
- method:表示类型映射的方法,有in,out,csin,csout等等,不同的方法具有不同的功能。
- typelist:表示匹配模式,如int,int分别表示要映射int与int类型。格式如下:
typelist : typepattern [, typepattern, typepattern, ... ] ;
typepattern : type [ (parms) ]
| type name [ (parms) ]
| ( typelist ) [ (parms) ]
支持一次匹配多种类型。
- code:为了实现这个类型映射而编写的代码,格式如下:
code : { ... }
| " ... "
| %{ ... %}
即用大括号、双引号、百分号都可以。
以下都是正确的指令使用方式:
// 用大括号定义code
%typemap(in) int {
$1 = example($input); // 自定义的代码
}
// 用双引号定义code
%typemap(in) int "$1 = example($input);"
// 用百分号定义code
%typemap(in) int %{
$1 = example($input);
%}
// int short long 三种类型都使用这个typemap
%typemap(in) int, short, long {
$1 = example($input);
}
// 函数的第一个参数为char*类型且参数名为str,且第二个参数类型为int参数名为len的函数使用此typemap
// 或者函数的第一个参数为char*类型参数名为buffer, 且第二个参数类型为int参数名为size的函数使用此typemap
%typemap(in) (char *str, int len),(char *buffer, int size)
{
$1 = getString($input);
$2 = getSize($input);
}
// 函数int *output (int temp)或者函数long *output (long temp) 才能使用此tyemap
%typemap(in) int *output (int temp),long *output (long temp)
{
$1 = &temp;
}
三、Typemap都可以用来做什么?
3.1 入参映射处理
处理函数的入参void foo(int a, double b, char * s);
- 入参处理(“
in
”方法) - 入参类型检查(“
typecheck
”方法) - 出参处理(“
argout
”方法) - 检查入参值(“
check
”方法) - 入参值初始化(“
arginit
”方法) - 参数默认值(“
default
”方法) - 入参内存管理(“
freearg
”方法)
3.2 函数返回值映射处理
如int fool()
。
- 返回值处理(“
out
”方法) - 返回值内存管理(“
ret
”方法) - 返回新对象内存管理(“
newfree
”方法)
3.3 异常处理
int foo() throw(MemoryError)
- 处理C++的异常("
throw
"方法)
3.4 全局变量处理
C++全局的int foo;
。
- 赋值(“
varin
”方法) - 读取(“
varout
”方法)
3.5 其它
- 成员变量处理 (“
memberin
”方法) - 创建常量 (“
consttab
”,”constcode
”方法)
3.6 哪些做不到?
无法转换参数调用顺序:
void foo(int, char*);
// 参数调用顺序不一样,typemap做不到
Foo(“hello”,3);
四、typemap的作用域与删除
作用于:遵循就近原则。
// typemap1
%typemap(in) int {
...
}
int fact(int); // typemap1
int gcd(int x, int y); // typemap1
// typemap2
%typemap(in) int {
...
}
int isprime(int); // typemap2 就近原则
删除:重新定义一次即可,不过不要写大括号。
%typemap(in) int; //删除int类型的in方法的typemap
%typemap(in) int, long, short; //删除int,long,short类型的in方法
%typemap(in) int *output;
五、Typemap匹配规则
基本规则:根据TYPE和NAME,流程如下:
- 优先寻找TYPE和NAME都匹配的typemap
- 其次寻找TYPE匹配的typemap
- 如果TYPE是C++的模板,如
T<TParam>
:
- 优先匹配T和NAME
- 其次匹配T
- 如果TYPE包含限定符(
const\volatile
等),会忽略限定符进行匹配。 - 如果TYPE是数组,则会将维度替换为
[ANY]
进行匹配
int foo(const char *s);
对于上述代码,以下的typemap都能匹配上,只是越来越粗:
%typemap(in) const char *s {} //类型和名称完全匹配
%typemap(in) const char * {} //类型匹配(无名称)
%typemap(in) char *s {} //类型和名称完全匹配(无限定符)
%typemap(in) char * {} //类型匹配(无限定符)
更多匹配示例如下:
最后一个称之为回归匹配,虽然我们没有直接定义Integer
类型的typemap
,但是定义了int
等价于Integer
,所以仍能匹配到typemap1。
除了一次匹配一个参数类型,我们还是使用括号进行多参数类型同时匹配:
%typemap(in) (char *buffer, int len) { // typemap 1 }
%typemap(in) char *buffer { // typemap 2 }
void foo(char *buffer, int len, int count); // 匹配typemap1 虽然参数个数不一样,但是前两种参数类型和参数名一样
void bar(char *buffer, int blah); // 匹配typemap2
typemap的默认匹配规则
作用:当已知的%typemap
都无法匹配这个类型时,则回退到SWIG的默认匹配规则。其默认匹配规则定义如下:
%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 {... }
其实现如下:
%typemap(in) SWIGTYPE * %{ $1 = ($1_ltype)$input; %}
%typemap(out) SWIGTYPE * %{ $result = (void *)$1; %}
%typemap(out) SWIGTYPE & %{ $result = (void *)$1; %} %typemap(out) SWIGTYPE && %{ $result = (void *)$1; %}
…
…
...
SWIGTYPE
是SWIG的关键字,可匹配任何类型的TYPE。假如我们认为SWIG默认的指针类型的匹配不会不符合预期时,可以进行重新定义覆盖:
//自定义 指针类型的行为
%typemap(in) SWIGTYPE * %{ …my code… %}
六、代码生成规则
SWIG根据%typemap的code定义进行代码生成,会将code插入到wrapper.cpp包装函数中。
class Foo
{
public:
void bar(int* a);
};
%typemap(in) int* {
int a=1;
$1=myHandle($input);
a=0;
}
上述typemap的code部分,被添加到了这里:
而且是一个单独的作用域,对wrapper.cpp的其它部分不可见、不被访问。
声明局部变量
适用:需要保存作用域内的值,让wrapper.cpp的其它部分也可以访问。
class Foo
{
public:
void bar(int* a);
};
%typemap(in) int* (int temp){
int a=myHandle($input);
temp=a;
$1=&temp;
}
在typemap的类型后面添加一个(int temp)
即可:
在包装函数里生成了一个名为temp2
变量(名字并非严格对应,不影响使用)。
七、内置的一些特殊变量
几个常用的说明如下:
变量名 |
说明 |
$n |
第几个参数。$1:第一个 $2:第二个 |
$argnum |
参数个数 |
$n_name |
第N个参数的名称 |
$n_type |
第N个参数的类型 |
$n_ltype |
第N个参数的ltype |
$n_mangle |
第N个参数包装之后的类型,如_p_Foo |
$n_descriptor |
第N个参数包装之后的类型完整名,如SWIGTYPE_p_Foo |
$*n_type |
第N个参数移除一个指针之后的类型 |
$&n_type |
第N个参数添加一个指针之后的类型 |
包装代码里具体表示如下:
理解这些内置变量的意思,有助于写出正确的typemap。
八、常用的typemap方法
8.1 in 方法
入参处理,用来将目标语言(如C#)的类型转为C++的类型。如:
%typemap(in) int {
$1 = myConvert($input);
}
可用的特殊变量为:
-
$input
: 入参的参数名 -
$symname
: C/C++的函数名
8.2 out 方法
注意:不是出参处理,而是函数返回值类型处理。
%typemap(out) int {
$result = myConvert($1);
}
可用的特殊变量:
-
$result
: 要返回的结果 -
$symname
: C/C++的函数名
8.3 argout 方法
这个才是函数的出参处理,常配合in方法一起使用。
%typemap(in, numinputs=0) int *out (int temp) {
$1 = &temp;
}
%typemap(argout) int *out {
// Append output value $1 to $result
}
可用的特殊变量:
-
$result
: 要返回的结果 -
$input
: 入参的参数名 -
$symname
: C/C++的函数名
8.4 freearg 方法
内存管理相关,常用于当包装的函数退出时,清理开发者自己分配的内存资源。
%typemap(in) int *items {
int nitems = Length($input);
$1 = (int *) malloc(sizeof(int)*nitems);
}
%typemap(freearg) int *items { free($1);}
生成包装代码:
8.5 ret 方法
内存管理相关,清理返回值内存。使用的不是很多,多数情况下其功能可被out 方法替代。
%typemap(ret) char* getStringIDData{
delete[] $1; // 手动释放
$1=nullptr;
}
生成代码如下:
由于原有的getStringIDData
函数内使用了new
分配内存,SWIG会将char*
自动转为string
,这个转化过程时值复制,所以会有内存泄漏问题,需要我们手动释放。