SWIG包装器使用指南——(三)Typemap 类型映射

时间:2023-01-31 01:31:07



文章目录

  • 一、什么是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这种我们无法直接使用的类型:

SWIG包装器使用指南——(三)Typemap 类型映射


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,流程如下:

  1. 优先寻找TYPE和NAME都匹配的typemap
  2. 其次寻找TYPE匹配的typemap
  3. 如果TYPE是C++的模板,如T<TParam>:
  • 优先匹配T和NAME
  • 其次匹配T
  1. 如果TYPE包含限定符(const\volatile等),会忽略限定符进行匹配。
  2. 如果TYPE是数组,则会将维度替换为[ANY]进行匹配

int foo(const char *s); 对于上述代码,以下的typemap都能匹配上,只是越来越粗:

%typemap(in) const char *s {}     //类型和名称完全匹配
%typemap(in) const char *  {}      //类型匹配(无名称)
%typemap(in) char *s       {}         //类型和名称完全匹配(无限定符)
%typemap(in) char *        {}          //类型匹配(无限定符)

更多匹配示例如下:

SWIG包装器使用指南——(三)Typemap 类型映射


最后一个称之为回归匹配,虽然我们没有直接定义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部分,被添加到了这里:

SWIG包装器使用指南——(三)Typemap 类型映射


而且是一个单独的作用域,对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)即可:

SWIG包装器使用指南——(三)Typemap 类型映射


在包装函数里生成了一个名为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个参数添加一个指针之后的类型

包装代码里具体表示如下:

SWIG包装器使用指南——(三)Typemap 类型映射


理解这些内置变量的意思,有助于写出正确的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);}

生成包装代码:

SWIG包装器使用指南——(三)Typemap 类型映射

8.5 ret 方法

内存管理相关,清理返回值内存。使用的不是很多,多数情况下其功能可被out 方法替代。

%typemap(ret) char* getStringIDData{
delete[] $1; // 手动释放
$1=nullptr;
}

生成代码如下:

SWIG包装器使用指南——(三)Typemap 类型映射


由于原有的getStringIDData函数内使用了new分配内存,SWIG会将char*自动转为string,这个转化过程时值复制,所以会有内存泄漏问题,需要我们手动释放。