python展开 c函数中的宏预处理_SWIG之为C/C++的API生成Python调用接口基础

现在游戏业务的cache基本都是接入tcaplus,以前业务自己拥有cache的时候我们可以很方便去通过脚本去访问玩家的数据,所以最近想能不能对tcaplus生成Python的访问接口,由于tcaplus只提供了C++ API屏蔽了底层的协议数据细节,不太好直接写Python访问接口,偶然间接触到了SWIG,差不多花了一周时间,通过官方手册完成了SWIG对Tcaplus Python 接口的封装。

这一篇文章主要是介绍在看手册过程中遇到的一些问题和思考总结, 并没有涵盖所有的点,因为SWIG3.0的手册有653页,还是英文的,看手册主要还是为了能够封装Tcaplus Python访问接口,所以只是SWIG一部分比较重要的知识点,下面就简单介绍一下SWIG的一些基础概念和对API进行Python封装的实现原理,欢迎批评指正。

1. SWIG的产生

SWIG(Simplified Wrapper and Interface Generator),一个包装和接口生成器工具,可以为C/C++程序构建生成各种脚本语言的调用接口,这样就可以通过脚本语言来直接调用C/C++编写的程序。最初在1995年由一群实验室的大佬为了解决在大量模拟数据下,经常变换不同的脚本语言来解决问题的需要。 可以说:SWIG存在的目的就是为了简化C/C++和其他脚本语言的交互。

SWIG的产生

为什么要用脚本语言去调用C/C++库呢,应为C/C++有以下众所周知的优点:

高性能

操作系统级的编程

庞大的用户群体和社区

当然C/C++作为静态类型的语言也有如下的一些不友好:

写UI很痛苦:MFC, X11,GTK

对于修改逻辑需要重新编译,这是静态语言很不友好的地方

模块化很麻烦

不安全:空指针,内存溢出,泄漏等

为了解决这些限制,许多程序员得出的结论是:用不同的语言来做不同的事情。例如通过Python或者Tcl来编写图形化界面,Java更容易编写分布式计算的软件。不同的编程语言有不同的长处和弱点,任何编程语言都不可能是完美的。因此,通过组合语言您可以共同利用每种语言的最佳特性,并大大简化软件开发的过程。

2. SWIG脚本语言如何和C进行交互

首先声明本文中只以Python语言来阐述SWIG的使用。

理解脚本语言如何和C/C++交互,首先简单说一下Python的标准实现CPython,Python标准的解析器实现是由C编写的,基础功能模块也都是C编写的,然后将其编译成了python解析器和相关so, 所以对于CPython来说,其本身解析过程最终都是通过执行底层C代码来进行实现的。

官方标准CPython提供了对应的API允许对Python进行扩展,CPython扩展需要在C/C++代码中嵌入很多中的API,为了能够调用C/C++的函数,需要声明如何调用函数,参数的类型转换等等,很麻烦。最终将C/C++代码编译成so,在Python中进行加载和使用。

那么SWIG的目的就是要为C/C++ API提供脚本语言的接口,SWIG所有做的就是解决脚本语言和C/C++交互的问题,SWIG所做的事情其实就是两件事:

根据要调用的C API生成Wrapper函数,作为胶水来让脚本解析器和底层C函数进行交互.

为生成的Wrapper函数生成脚本语言的调用接口。

这样完成了对C/C++函数脚本语言接口的生成,通过直接使用脚本语言的接口,会调用对应的Wrapper函数,Wrapper函数会将脚本语言传入的参数,转换成C的参数,然后调用对应的C的接口,执行完后,Wrapper函数会将C返回的结果,转换成脚本语言的数据类型返回给脚本上层。

为了说过SWIG是如何做的,这里举一个SWIG手册上的一个例子:

example.c中有一个C写的fact函数,我们希望在Python中进行调用:

1

2

3

4

5

6

7// example.c

int fact(int n){

if (n <= 1)

return 1;

else

return n*fact(n-1);

}

SWIG需要一个类似IDL的接口文件,来描述需要Wrapper的C函数fact(),如下:具体语法后面会介绍:

1

2

3

4

5

6

7

8// example.i

%module example

%{

// 此部分会直接拷贝到wrapper文件中,用于编译

extern int fact(int);

%}

// 比部分是SWIG需要进行Wrapper的部分

extern int fact(int);

然后执行以下命令就会最终生成_example.so(只能生成动态库哦,如果生成静态库要重编python),用来在Python中引入:

1

2

3swig -python example.i

gcc -c -fpic example.c example_wrap.c -I/usr/include/python2.7

gcc -shared example.o example_wrap.o -o _example.so

swig –python example.i命令会生成两个文件:example_wrap.c和example.py

可以看到example_wrap.c中对于fact()函数生成的wrapper函数如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26SWIGINTERN PyObject *_wrap_fact(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {

PyObject *resultobj = 0;

int arg1 ;

int val1 ;

int ecode1 = 0 ;

PyObject * obj0 = 0 ;

int result;

// 1. 解析Python传入PyObject*参数args,转换成C类型的int参数val1

if (!PyArg_ParseTuple(args,(char *)"O:fact",&obj0)) SWIG_fail;

ecode1 = SWIG_AsVal_int(obj0, &val1);

if (!SWIG_IsOK(ecode1)) {

SWIG_exception_fail(SWIG_ArgError(ecode1), "in method '" "fact" "', argument " "1"" of type '" "int""'");

}

arg1 = (int)(val1);

// 2. 调用C函数fact()

result = (int)fact(arg1);

// 3. 将C函数fact()返回结果转换成PyObject类型的数据,返回给Python调用

resultobj = SWIG_From_int((int)(result));

return resultobj;

fail:

return NULL;

}

生成对应的example.py中对于fact封装的脚本接口如下:

1

2

3

4

5// example.py

def fact(arg1):

return _example.fact(arg1)

fact = _example.fact

Python中执行如下:

1

2

3>>>import example

>>>example.fact(4)

24

看了这些应该对SWIG的工作原理了解了吧,上述wrapper的代码其实了解Python扩展的人就知道,就是调用Python扩展提供的API来实现的,在Python/C API的官方手册中有介绍用于编写Python扩展的很多接口,其实编写Python扩展不仅仅是上面我截取的wrapper函数,还有其他很多要注意的,所以对于想要为C/C++库直接通过这些API进行Python扩展编写还是很耗费时间和经历的,所以SWIG帮我们做了这些事情,还是很方便的。我们只需要为SWIG编写一个IDL文件,此外我们还要要做的可能就是为要使用的C/C++库再封装一层,这样就不会对整个要使用的API进行wrapper了,省去很多麻烦和学习成本。

3. SWIG基础

下面就是说明一下SWIG一些基础知识,里面基本都是SWIG官方手册中的内容,我梳理了一些重要的和我在封装tcaplus API遇到需要关心的进行了一些说明,希望能够对大家有所帮助:

3.1 SWIG输入

SWIG的使用在前面中简单说明了,这里介绍一下SWIG 输入接口的一些语法。

SWIG的输入即接口文件通常是以”.i” 或者”.swg” 为后缀,类似于其他IDL。接口文件包含一下内容:

wrapper中要使用的C/C++声明

SWIG指令

要wrap的C/C++数据,函数,结构体/类

一些情况下,SWIG可以直接通过原生的header file或者source file(强烈不要使用源文件特别是C++源文件)来生成Wrapper代码,但更多的情况是需要结合SWIG指令的。

SWIG接口文件的通常格式如下:

1

2

3

4

5

6

7%module mymodule

%{

#include "myheader.h"

%}

// Now list ANSI C/C++ declarations

int foo;

int bar(int x);

%module指令后面表示生成脚本语言对应的模块名字

%{}%指令之间的代码会直接拷贝到SWIG生成的wrapper源码文件中。这之间的代码一般就是头文件和一些声明,它们是生成的wrapper源码编译所必须的。

第三部分就是SWIG需要wrapper的C/C++函数、类的声明,SWIG会对这部分的C/C++函数、参数进行封装,最终将脚本调用的函数和参数转换成C函数和参数,然后调用对应的C函数(声明放在%{…%}部分)

SWIG接口文件中%开头的都是SWIG指令部分,由SWIG的进行解析。

SWIG的IDL语法是支持预处理的。和C语法基本都是类似的,包括:文件引入include/import,条件编译ifdef,宏展开等。

SWIG支持通过%include来引入文件到接口文件中,SWIG的%include就是C中的include所做的预处理,仅仅做内容的引入,但SWIG中的%include指令对同一个文件只会引入一次,不需要额外的include保护。

SWIG中在没有指令%{…%}包裹下直接使用#include声明是会被SWIG忽略的,除非在执行SWIG命令时加上-includeall选项,SWIG之所以对C/C++ 传统的#include进行忽略是因为:我们并不是希望SWIG去包装引入的头文件中的所有系统头文件和一些辅助文件。所以由此就可以知道%include引入的文件,会忽略其中的#include部分。

SWIG的%include指令的搜索路径顺序如下:

当前目录

swig -I 选项设置的目录

当前目录下的./swig_lib目录

swig -swiglib命令保存的目录,该命令执行结果是swig安装所在的目录:例如:/usr/local/share/swig/1.3.30

3.2 SWIG和C++

因为C++的复杂性,尤其到C++11你会发现你像学习一门新语言,所以对于SWIG来说不能够支持C++的所有特性,比较SWIG只是一个解析器,做不到GCC的全部功能。但SWIG版本一直也在升级,慢慢也会做到完善,但对于使用来说,基本没有问题,因为我们很多时候都会基于要使用库再进行一些封装,屏蔽一些无用的接口,这要也很大程度上可以减少SWIG的wrap复杂度,下面是中贴出的支持的C++特性:

Classes

Constructors and destructors

Virtual functions

Public inheritance (including multiple inheritance)

Static functions

Function and method overloading

Operator overloading for many standard operators

References

Templates (including specialization and member templates)

Pointers to members

Namespaces

Default parameters

Smart pointers

其中贴出来了目前不支持的C++特性:

Overloaded versions of certain operators (new, delete, etc.)

当然还有一些其他特殊语法,SWIG也是不支持的,这里没有贴出来,

3.3 SWIG对于C/C++参数类型的处理

C/C++中参数类型可以是值传递,指针,引用来区分,也可以按内置数据类型和自定义数据类型来区分,要想在脚本语言对这些类型种类进行一一区分,SWIG需要在生成的wrapper代码中和脚本语言接口中进行分装,以最终达到操作底层的C指针的方式。

3.3.1 对于整型和浮点类型的指针和引用参数的处理

下面看一个示例:我们希望调用一个add()的C函数,将x+y的结果通过result返回:

1void add(int x, int y, int *result);

SWIG的库定义了pointer_functions的macro支持:希望(int, double)类型的指针(引用)做为函数参数,用来传入或则是传出结果使用。注意不支持char*哦

1

2

3

4

5

6

7

8

9

10

11

12// interface file

%include

%pointer_functions(int, intp); #用来创建int指针对象的一系列接口定义

void add(int x, int y, int *result);

//python

>>> import example

>>> intobj = example.new_intp() # 创建一个"int" 对象用来存储结果

>>> example.add(3, 4, intobj)

>>> example.intp_value(intobj) # 取出结果

7

>> example.delete_intp(intobj) # 删除对象

我们可以看看pointer_functions的SWIG宏定义:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46//

%define %pointer_functions(TYPE,NAME)

%{

static TYPE *new_##NAME() { %}

#ifdef __cplusplus

%{ return new TYPE(); %}

#else

%{ return (TYPE *) calloc(1,sizeof(TYPE)); %}

#endif

%{}

static TYPE *copy_##NAME(TYPE value) { %}

#ifdef __cplusplus

%{ return new TYPE(value); %}

#else

%{ TYPE *obj = (TYPE *) calloc(1,sizeof(TYPE));

*obj = value;

return obj; %}

#endif

%{}

static void delete_##NAME(TYPE *obj) { %}

#ifdef __cplusplus

%{ if (obj) delete obj; %}

#else

%{ if (obj) free(obj); %}

#endif

%{}

static void NAME ##_assign(TYPE *obj, TYPE value) {

*obj = value;

}

static TYPE NAME ##_value(TYPE *obj) {

return *obj;

}

%}

TYPE *new_##NAME();

TYPE *copy_##NAME(TYPE value);

void delete_##NAME(TYPE *obj);

void NAME##_assign(TYPE *obj, TYPE value);

TYPE NAME##_value(TYPE *obj);

%enddef

上述宏定义分别定义了intp在C中的实现,以及用于生成脚本访问接口和wrapper代码的声明。wrapper代码中又会对intp进行type*类型进行PyObject的封装:

1

2

3

4

5

6

7

8

9

10

11SWIGINTERN PyObject *_wrap_new_intp(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {

PyObject *resultobj = 0;

int *result = 0 ;

if (!PyArg_ParseTuple(args,(char *)":new_intp")) SWIG_fail;

result = (int *)new_intp();

resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_int, 0 | 0 );

return resultobj;

fail:

return NULL;

}

最终你会发现intp的所有操作都是在C层面进行实现的,每一步都需要在Python和C库之间进行交互:上述python执行过程如下:

当执行intobj = example.new_intp(),会在C层面new了一个int,返回给Python一个对象;

当在Python中调用example.add(3, 4, intobj)时,add的wrapper代码就会将传入的intobj转换成一个C层面的指针,这个指针在是在调用new_intp创建出来的,C层面的add函数会直接将结果保存在new出来的空间中(即intobj封装的);

执行完后通过example.intp_value(intobj)取出结果,intp_value会将C层面new出来的int中的数据返回到Python中;

最终执行example.delete_intp(intobj)来释放C层面new出来的int对象。

上面四个过程都是需要与C函数进行交互来实现,其实这里还有一个%pointer_class宏功能和pointer_function一样,不过pointer_class会将对应的类型封装成一个Python的类而不是很多函数接口:且它的好处就是不需要额外的调用delete接口来释放C层面new出来的对象:

1

2

3

4

5

6

7

8

9

10

11// 接口文件定义

%include

%pointer_class (int, intp); #用来创建int指针对象

void add(int x, int y, int *result);

//Python

>>> import example

>>> c = example.intp()

>>> example.add(3, 4, c)

>>> c.value()

7

我们可以看出pointer_class的接口根据简单清晰,符合面向对象的思想,且不需要额外手动的GC操作,所以建议使用pointer_class。

SWIG的库定义的macros支持:希望函数参数是(int, double)数组的,不支持指针和其他复杂数据类型,也不支持char,和一样,有两个宏:

1

2%array_functions(type, name) # 定义了数组操作的一系列接口,不建议使用

%array_class(type, name) # 定义了数组对象,免GC,更好用,建议使用

我们知道C的数组参数同样是当做指针来处理的,定义的宏的功能同样是将数组对象的创建,操作,释放通过调用C函数来进行实现,这里就不贴出wrapper中生成的代码了,基本和中的pointer_functions()和ponter_class()一样,只不过是new int 和new int[n]的差别,以及多了对数组操作的接口。下面是array_class的使用:

1

2

3

4

5

6

7

8

9

10

11

12// 接口文件

%module example

%include "carrays.i"

%array_class(double, doubleArray); //定义double数组对象

void print_array(double x[10]);

//python执行

>>>import example

>>>c = example.doubleArray(10) # Create double[10]

>>>for i in range(0, 10):

c[i] = 2*i # Assign values

>>>example.print_array(c) # pass to C function

3.3.2 对于char指针参数的处理

对于char *参数的C接口,如果char*只是传入参数,表示一个字符串,那么完全不用担心,SWIG会自动封装好,在调用C接口时将Python的字符串转换成char*,但不允许在C中对该char*进行修改。对于Python来说,通过string来传入binary数据是能够透传到C接口中的char*的,不需要进行人工干预。如果想通过C接口中的char*透传binary数据到Python层面是需要进行干预的。

如果C接口中char*参数是希望能够进行写入数据的,就需要我们在IDL进行描述了。SWIG中的库定义的macros用来处理:希望函数参数可以是char指针,以此写入字符串或者是二进制数据

1

2

3

4

5

6inline void get_name(char * _name)

{

char name[100] = "123456789";

name[5] = 0;

memcpy(_name, name, 100);

}

可以通过:

1

2

3// interface file

%cstring_bounded_output(char *_name, 100);

void get_name(char * _name)

这个宏的使用方式是依靠函数的参数名字进行替换,如果头文件中含有很多char *path的参数名,都会进行处理,生成的脚本语言中传出的name会通过返回值来实现,

1

2

3>>>import example

>>>print example.get_name()

12345

下面是get_name对应SWIG生成的wrapper代码的实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17SWIGINTERN PyObject *_wrap_get_name(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {

PyObject *resultobj = 0;

char *arg1 = (char *) 0 ;

char temp1[100+1] ;

arg1 = (char *) temp1;

if (!PyArg_ParseTuple(args,(char *)":get_name")) SWIG_fail;

get_name(arg1);

resultobj = SWIG_Py_Void();

arg1[100] = 0;

// 这里最终是调用SWIG_FromCharPtr接口来返回字符串,这里是无法透传binary 数据的

resultobj = SWIG_Python_AppendOutput(resultobj, SWIG_FromCharPtr(arg1));

return resultobj;

fail:

return NULL;

}

如果想要通过char*透传出binary数据可以通过%cstring_chunk_output(parm, chunksize)宏,我们可以看一下两者生成的wrapper代码的差异,如下图:其实最终都是调用了Py扩展中的API,在处理char*的时候是按NULL来结束还是获取固定长度的串。

3.3.3 对于自定义类型参数的处理

对于前面两小节介绍了SWIG对于c内置数据类型指针,引用,数组的处理,对于内置数据类型,SWIG都会封装成一个对应指针类型PyObject参数, 当调用时,需要定义对应的wrapper 数据类型才可以。

但对于自定义的数据类型,SWIG对自定义数据类型进行了统一wrap, 能够让Python中的对象和C中的 Pointers,references, values, and arrays自动转换。如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14// C函数声明

void spam1(Foo *x); // Pass by pointer

void spam2(Foo &x); // Pass by reference

void spam3(const Foo &x);// Pass by const reference

void spam4(Foo x); // Pass by value

void spam5(Foo x[]); // Array of objects

// Python中的调用

>>> f = Foo() # Create a Foo

>>> spam1(f) # Ok. Pointer

>>> spam2(f) # Ok. Reference

>>> spam3(f) # Ok. Const reference

>>> spam4(f) # Ok. Value.

>>> spam5(f) # Ok. Array (1 element)

3.4 SWIG对于C++的class生成的Proxy class分析

SWIG为了能够让C++的class和目标脚本语言之间很好的映射,SWIG为目标语言生成了一个基本一致的Proxy class,例如在Python中:

1

2

3

4

5

6

7

8

9

10

11

12

13// example.h

class Bob

{

public:

Bob() : bob(1)

{}

int32_t get_bob() const;

int32_t set_bob(int32_t _bob);

private:

int32_t bob;

};

生成的Python代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23class Bob(_object):

__swig_setmethods__ = {}

__setattr__ = lambda self, name, value: _swig_setattr(self, Bob, name, value)

__swig_getmethods__ = {}

__getattr__ = lambda self, name: _swig_getattr(self, Bob, name)

__repr__ = _swig_repr

def __init__(self):

this = _example.new_Bob()

try:

self.this.append(this)

except Exception:

self.this = this

def get_bob(self):

return _example.Bob_get_bob(self)

def set_bob(self, _bob):

return _example.Bob_set_bob(self, _bob)

__swig_destroy__ = _example.delete_Bob

__del__ = lambda self: None

Bob_swigregister = _example.Bob_swigregister

Bob_swigregister(Bob)

是不是对使用者来说基本相当于调用C++类,其实通过之前的内容我们应该清楚了SWIG通过生成wrapper代码来调用对应的C功能代码,而脚本和wrapper代码的通信是直接通过Python约定的扩展API进行交互,我们可以看看生成的Bob类的wrapper代码的样子:

这里是_init_(self)中调用的_example.new_Bob()的代码,可以看出在wrapper代码中Python的Bob对象的构建最终会在C层面new一个对应的Bob对象。

1

2

3

4

5

6

7

8

9

10

11SWIGINTERN PyObject *_wrap_new_Bob(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {

PyObject *resultobj = 0;

Bob *result = 0 ;

if (!PyArg_ParseTuple(args,(char *)":new_Bob")) SWIG_fail;

result = (Bob *)new Bob();

resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_Bob, SWIG_POINTER_NEW | 0 );

return resultobj;

fail:

return NULL;

}

Bob类中其他成员函数的wrapper代码基本都是一样的实现逻辑,这里就不贴出来了 。

3.4.1 tempaltes指令

C++中的template类和函数是一个相对C来说是很牛逼的特性,对于C++的template C++编译器会在编译期间根据调用代码实例化生成具体的类和函数代码。如果没有对于模版类型的调用,是不会生成template相关的实例化代码的。

所以要想SWIG对tempalte相关的的代码进行wrap,就需要告诉SWIG需要wrap 模版类或函数具体的实例化。

SWIG 1.3.7就支持了C++ template的包装,template的包装需要注意以下两点:

需要告知SWIG解析器template怎样实例化:vector …etc

template实例化的名字,例如vector在很多脚本语言中不是合法的标识符,所以需要为Wrapper生成的proxy class定义一个更合理的实例化名,例如intvector等。

对于C++中的各种STL容器类,SWIG封装好了一个库,当要wrap的代码中有用到STL容器的时候引入这些库,就不需要对STL的头文件进行Wrapper了,那样会很麻烦,且生成很多额外且无用的wrapper代码.例如如使用到std::vector,std::vector, std::vector<:string>就需要接口文件进行如下定义:

1

2

3

4

5

6

7

8

9

10// interface file

%include

%include

namespace std

{

%template(IntVector) vector;

%template(DoubleVector) vector;

%template(StringVector) vector;

}

下面是关于tempalte class的一个实例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19// example.h

template

class Alice

{

public:

void set_alice(T _alice){

alice = _alice;

}

T get_alice(){

return alice;

}

private:

T alice;

};

// example.i

%include "example.h"

//SWIG要包装Alice模版实例化的Alice类

%template(IntAlice) Alice;

生成的Python代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23class IntAlice(_object):

__swig_setmethods__ = {}

__setattr__ = lambda self, name, value: _swig_setattr(self, IntAlice, name, value)

__swig_getmethods__ = {}

__getattr__ = lambda self, name: _swig_getattr(self, IntAlice, name)

__repr__ = _swig_repr

def set_alice(self, _alice):

return _example.IntAlice_set_alice(self, _alice)

def get_alice(self):

return _example.IntAlice_get_alice(self)

def __init__(self):

this = _example.new_IntAlice()

try:

self.this.append(this)

except Exception:

self.this = this

__swig_destroy__ = _example.delete_IntAlice

__del__ = lambda self: None

IntAlice_swigregister = _example.IntAlice_swigregister

IntAlice_swigregister(IntAlice)

3.4.2 struct中的数组成员

对于struct中或class的公有数组成员,这里单独拿出来说是因为和之前对于指针和数组类型的参数处理是类似的,需要我们在IDL中进行定义。

1

2

3

4

5

6

7

8

9

10

11

12

13

14// example.h

struct Bob{

int bob[10];

};

// example.i

%module example

%{

#include "example.h"

%}

%include

%array_class(int, intArray)

%include "example.h"

通过%array_class定义一个intArray这样就可以set结构体中的数组成员了:

1

2

3

4

5

6

7// python

import example

bob_obj = example.Bob()

data =example.intArray(10)

for i in xrange(0,10):

data[i] = i

bob_obj.bob = data

但是你会发现无法在python中去get数组成员。因为你直接访问Bob对象的bob数组没有转换成intArray的接口,且bob是不可遍历的。那怎么办呢,如何访问结构体中的数组成员呢,这个时候就需要通过SWIG的typemap指令了(typemap具体含义和使用方式在下一节中阐述),接口文件修改如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20// example.i

%module example

%{

#include "example.h"

%}

%include

%array_class(int, intArray)

// 定义关于int bob[10] get方法的处理方式,通过Python C API来使结构体中的指针(数组)转换成Python中的list传出,这样就可以在Python中直接访问了

%typemap(out) int bob[10]

{

$result = PyList_New(10);

for(int i = 0; i < 10; ++i)

{

PyObject *obj = PyInt_FromLong(long($1[i]));

PyList_SetItem(resultobj, i, obj);

}

}

%include "example.h"

可以看到:wrapper代码中bob_get方法的对于类型转换的方法被重写如下:

这样就可以在Python中访问set之后的bob数据了:

1

2

3// python

>>>bob_obj.bob

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

3.5 SWIG的Typemaps

typemap指令顾名思义是类型映射的意思,用来修改SWIG对脚本语言数据类型和C数据类型的转换行为。使用typemap的前提是需要对Python C API需要有一定的了解。其实在大多数情况下,SWIG的默认封装都是能够满足需求的,typemap是一个高度自定义化的功能扩展。

typemap指令实际是用来修改SWIG对特定C数据类型和脚本数据类型的默认转换规则。typemap语法如下:

1

2

3

4

5

6

7

8

9

10

11// typemap有method, typelist, code三部分组成

%typemap(method [, modifiers]) typelist code

// typelist是要修改的类型列表,支持多种类型

typelist : typepattern [, typepattern, typepattern, ... ]

// 类型的格式支持类型名+参数名+数组括号+数组长度

// 例如:int a, char a[], char a[100]

typepattern : type [ (parms) ]

| type name [ (parms) ]

| ( typelist ) [ (parms) ]

typemap的method主要有‘in’, ‘out’等,详见< SWIG3.0 11.5 Common typemap methods>一节。

1

2

3

4

5

6

7

8

9

10// in 方法用于修改Python的int转换成C int的方式

// $input代码python的传入, $1代码转换后调用C接口时传入的参数

%typemap(in) int {

$1 = PyInt_AsLong($input);

}

// out 方法用于修改C int转换成Python的int的方式

// $1表示C中要传出的数据,$result表示传给Python的数据

%typemap(out) int {

$result = PyInt_FromLong($1);

}

typemap会将匹配到的类型的默认wrapper中的Python和C关于该类型的转换逻辑都进行替换。前面中已经用typemap来处理int[]成员的get方法,这里再通过结构体中的字符串数组来阐述typemap的强大功能:

1

2

3

4struct Tom

{

char tom_name[100];

};

对于是否有一下typemap的定义,可以看到下图的差异:

1

2

3

4

5// interface file

%typemap(out) char tom_name[100]

{

$result = PyString_FromStringAndSize($1, 100);

}

SWIG wrap的代码可以看到差异如下:

默认的情况下,是无法通过tom_name获取binary data的,因为有默认值通过strlen截断,加入要想通过tom_name返回binary data,就要修改其默认转换行为,是不是很厉害。

4. 参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值