初识代码封装工具SWIG(回调Python函数)

这不是我最先使用swig了,以前在写Kynetix的时候就使用了swig为python封装了C语言写的扩展模块。可是当时我对C++还不是很了解,对其中的一些概念也只是拿来直接用,没有理解究竟是什么,为何会有这种功能。因此昨天我又拿出了《python科学计算》这本书来温习了一下swig那一部分,果真对swig又有了新的认识。java

对swig真正全的使用都在swig的文档中有详细的介绍,并且因为swig支持不少种语言,例如java、python、Tcl等,所以这份文档内容至关的丰富。因为如今尚未很好的中文资源,因此如今只能默默的看英文文档了。python

SWIG用来作什么?

swig是个封装器,它读取C/C++的函数或者类声明,并将这些函数或者类进行封装,生成一个封装代码,其中包含一个目标语言的文件供目标文件调用,还包含一个C/C++的封装文件以<source>_wrap.c或者<source>_wrap.cxx命名。这个生成的_wrap.cxx文件就是个封装文件,而后咱们将这个封装文件同咱们本身的C/C++代码或者是已经编译好的目标文件(*.o或者*.obj文件)或者库文件(*.lib/*.a文件)一块儿编译并链接,就会生成一个咱们目标语言可以认识而且调用的模块。c++

其中的swig生成的封装代码的做用就是可以使得python与C/C++之间可以无阻碍的沟通,也就是ruby

  • 将封装函数接收到的Python对象转换成C/C++可以处理的数据
  • 有了数据之后便执行C/C++的函数
  • 执行C/C++函数将返回值在转换成python对象返回给python代码去处理

简单的操做

swig须要一个*.i文件,也就是swig接口文件(interface)告诉swig须要如何处理C/C++的数据、函数和类。函数

  1. 先生成封装代码spa

    1
    $ swig -c++ -python demo.i

    这个命令就会生成python的封装代码,当前路径下会出现demo.pydemo_wrap.cxx文件。其中demo.py文件中是python代码,也就是一个壳子,他可以让python程序调用这个模块中的函数,可是函数的实体并不在里面,由于函数的实体是C/C++编译后的动态库文件。指针

  2. 编译封装代码
    这一步只说明生成的C/C++的封装代码是能够单独编译的,这就将封装同C/C++库分割开,我按照个人方式写C/C++代码不用管封装的事情,最后只要把wrap的目标文件连接起来就行了。code

    1
    $ g++ -fPIC -c demo_wrap.cxx

    须要注意的是-fPIC这个参数是必定要加的,否则就没法生成动态连接库。具体这个参数是作什么的,顾名思义就是生成一个位置独立的代码段,这样不管函数在哪都可以动态的调用这个动态库了,详见:http://stackoverflow.com/questions/5311515/gcc-fpic-option对象

  3. 连接成扩展模块
    将封装文件与库文件连接成为python可以调用的动态库,这一步就好像是使用wrap这个文件给C/C++库文件进行化妆,化成python认识的那种样子。固然swig也能够根据使用者的需求把C/C++的库化妆成其余语言认识的样子如java、ruby等。
    这样就会生成一个_demo.so或者_demo.pyd的库文件,python能够经过以前的demo.py或者直接_demo.pyd来用本身的方式调用C/C++的函数和类来为本身服务。blog

    1
    $ g++ -shared demo_wrap .o demo.c -o _demo.so

关于类型映射

.i文件描述了如何建立封装文件,具体的语法我不在这里总结了能够直接去看文档。其中比较重要的部分就是如何让python和C/C++进行交流,好比如何处理python没有指针操做与C/C++传入指针的矛盾,如何处理python返回多个值与C/C++只能返回一个值的矛盾等。

类型映射就是一套规则,告诉swig如何将这些矛盾化解,并定义名称参数,将名称参数写道接口文件的类和函数声明中,让swig处理。
SWIG已经有了默认的一些类型映射,例如* OUTPUT* INPUT* INOUT等来告诉swig这些参数处理成python接口时候怎么处理。
例如若是我在接口文件中声明了一个C函数

1
void add_multi(double x, double y, double * OUTPUT, double * OUTPUT);

 

这时候swig就认出了OUTPUT是一种类型映射定义的名称参数,C语言修改这两个指针指向的值要处理成python调用这个函数返回这两个指针指向的值的list。

除了使用已定义的类型映射,swig还支持自定义的类型映射,这里我也很少讲了,之后若是在写类型映射的时候我会更新。

回调Python函数

这里主要是可以让C/C++代码调用python的函数。如今必需要理解这一点,由于KMCLib中就用到了这个,使得可以让用户使用python自定义RateCalculator而后从新定义C++的虚函数,是C++程序可以调用python类的方法。
我在这里举一个例子,就是在python中可以继承C++中定义的类,而且在python中从新定义C++类的虚函数。

  1. 开启此功能,在接口文件中的模块名称定义中要加入director参数

    1
    % module(director="1") demo
  2. 在但愿可以调用python函数的C++类中,经过%feature指令开启director功能:

    1
    %feature( "director") Sum;

我下面把别人的例子贴上来,方便之后本身回忆:

定义一个求和类:

1
2
3
4
5
6
7
8
9
10
11
class Sum
{
public:
Sum() {};
 
~Sum() {};
 
double Cal(int start, int end); // 从start开始到end结束,将Func做用于中间的整数而后球和。
 
virtual double Func(double x) { return x }; // python 中的Sum类的子类能够重写这个虚函数
}

 

在接口文件中咱们放入此类的声明的时候开启”director”功能:

1
2
3
4
5
6
7
8
9
10
11
%feature( "director") Sum
{
public:
Sum() {};
 
~Sum() {};
 
double Cal(int start, int end);
 
virtual double Func(double x) { return x };
}

 

这样在python中咱们就能够这么用了:

1
2
3
4
5
import demo
 
class SumReciprocal(demo.Sum): # Sum类的派生类
def Func(self, x): # 重写Sum的Func虚方法
return 1/x

 

而后咱们就能够直接在python中使用这个重写过Sum类方法的子类了。

总结

swig很强大,可以熟练使用,是快速并且方便独立的构建python语言以及其余动态语言的扩展模块,真是感受我站在了巨人的肩膀上了。

http://pytlab.org/2016/04/02/%E5%88%9D%E8%AF%86%E4%BB%A3%E7%A0%81%E5%B0%81%E8%A3%85%E5%B7%A5%E5%85%B7SWIG/