使用swig将c和c++代码转化为python模块

前言

在python中使c/c++的方法有不少,最简单的一种方法就是讲c代码编译成动态连接库,在python中使用ctypes模块调用,不过这种方法对c++支持不够好。当前在实际项目中swig是将c/c++转为最常用的工具。

关于swig

swig是非常强大的开源工具,它将支持c/c++代码与绝大多数主流脚本语言相集成,使用swig将c/c++转化为python的模块很简单,由于要满足各种不同的需求,需要设置swig的参数也会变复制。

在windows下安装swig非常简单,在官方网站:http://www.swig.org/,下载安装包直接安装即可。

如何使用swig,swig官方网站提供非常详细的文档,针对python在文档中有专门的章节讲解。在本文中,我只会介绍简单的使用方法和我使用时遇到的问题。

使用swig

如果使用swig将c++转为python模块,按我自己的理解可以慨括为两个步骤:一个接口文件、两次编译过程。

  1. 接口文件(interface file)

    为了使用swig,你需要增加一个接口文件。 接口文件一般以.i为文件的后缀。接口文件的作用是,提取c/c++源文件中的接口函数或类型,以及定义一些特殊的功能,其形式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* example.i */
    %module example
    %{
    #define SWIG_FILE_WITH_INIT
    /* Put header files here or function declarations like below */
    #include "header.h"
    %}

    extern double My_variable;
    extern int fact(int n);
    extern int my_mod(int x, int y);
    extern char *get_time();

    可以把接口文件看做三个部分,第一部分是定义要生成的模块名,就是上面的第一行,第二部分就是包含的头文件信息,第三部分就是指定导出的函数。由于接口文件的存在,c/c++源文件中一般不需要像导出dll一样需要声明导出函数,只需要保持原样就可以了。

    为了简化接口文件,如果头文件中的开放函数都可以导出的化,可以导出部分也可以用头文件代替,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* example.i */
    %module example
    %{
    #define SWIG_FILE_WITH_INIT
    /* Put header files here or function declarations like below */
    #include "header.h"
    %}

    %include "header.h"
  2. swig编译

    根据写好的接口文件,用swig进行编译。vs的工程可以加入.i文件后,在.i文件上设置属性用swig编译,不过最简单还是直接用命令行来编译,一般对c++文件编译命令如下:

    1
    swig c++ -python example.i

    编译生成两个文件,一个后缀名为.cxx的c++文件,一个后缀名为.py的python文件,各自从c++和python的角度声明导出信息。

  3. 用c++编译器编译动态连接库

    在windows项目中,我直接新建vs工程,用vs进行编译。

    • 新建空的vc++工程,设置类型为dll工程

    新建dll工程

    • 将前面的.cxx文件和.i文件添加如工程,并不使用预编译头设置

    • 将在香茗居属性中加入python的文件目录和库目录

    swig工程属性

    完成以上操作,就可以编译生成release的dll文件

  4. 测试python模块

    将dll文件名字修改为下划线开头加模块名,后缀为.pyd,如:_modelname.pyd,并将其复制到第二部生成的.py文件一起,注意:两个文件名除了.pyd文件是下划线开头,都一样。在python环境下导入模块测试。

    注意:在c++代码中如果用到动态连接库,编译成python模块后同样需要这些库,一般将这些库可编译好的python模块放在同一个目录下就可以了

其他功能和注意点

  1. 数据类型定义

    类等用户自定义类型需要包含在.i文件中,不过一般使用头文件全部代替就可以了,但是有些用typedef的基本类型数据可能并没有包含进来,如有些头文件是vs的环境中包涵好的,在swig用命令行执行时,无法加入vs的环境中的头文件,所以最好手动将其定义拷贝到.i文件中。

  2. 指针类型

    python中没有指针类型,如果c/c++导出的函数的中有参数或者返回值时指针时,python中就无法使用它了,因此在swig中,专门提供生成指针的方法。

    在.i文件中,添加如下代码(生成指向int的指针),可以在python中提供生成指针的方法,具体在python中的使用方法还需参考官方文档:http://www.swig.org/Doc3.0/SWIGDocumentation.html#Library_nn3

    1
    2
    3
    %include "cpointer. i"
    /* Create some functions for working with "int *" */
    %pointer_functions(int, intp);

    除了上面的pointer_functions外,swig中还提供了很多与指针和内存分配相关操作的底层方法,可以在python中使用。

  3. typemap类型映射

    类型映射是为了解决方便c/c++到python中参数转换的问题,最方便的一个应用就是c++返回的字符串指针时,可以用typemap将其映射到python的str类型上,这样在python中调用函数时,其返回值就成了str了。在.i文件中添加如下代码:

    1
    2
    3
    4
    %typemap(out) char* 
    {
    $result = PyString_FromString($1)
    }

    注意:如果char换成uchar,我尝试以上映射并没有成功,而且在swig中很不推荐函数返回指针,因为swig在转换后调用会导致内存泄漏,因此一般需要将这类函数传回的指针改成使用参数传回

  4. 使用numpy

    当c/c++函数会传回大量数据时,在python中使用numpy接收是最方便的,但是numpy并不是python的标准自带库,所以要在swig中支持对numpy的,需要修改.i文件,具体参考numpy的文档说明:https://docs.scipy.org/doc/numpy-1.10.0/reference/swig.interface-file.html

    假定c/c++函数头为double rms(double* seq, int n),修改.i文件,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    %{
    #define SWIG_FILE_WITH_INIT
    #include "rms.h"
    %}

    %include "numpy.i"

    %init %{
    import_array();
    %}

    %apply (double* IN_ARRAY1, int DIM1) {(double* seq, int n)};
    %include "rms.h"

    上面的numpy.i文件可以直接在github上找到下载下来,放在源文件目录下,在vs工程属性中加入numpy头文件的路径,如我的路径为:D:\WinPython\python-3.4.4\Lib\site-packages\numpy\core\include\

    要注意的是,在python中使用该rms函数时,参数只需要传入一个整形值,表示numpy数组的长度,返回值为numpy的ndarray对象。

Compartir