SWIG简介

  |  

摘要: 梳理 Python 与 C 的交互方法

【对数据分析、人工智能、金融科技、风控服务感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:潮汐朝夕
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


SWIG 是一种软件开发工具,它将用 C 和 C++ 编写的程序与各种高级编程语言连接起来,目标语言包括 JavaScript、Python、Go、Java 等。

SWIG 通常用于解析 C/C++ 接口并生成上述目标语言调用 C/C++ 代码所需的“粘合代码”。SWIG 还可以以 XML 的形式导出其解析树。

比较常见的场景是在 python 中调用 c/c++ 代码,本文通过两个例子看一下如何用 SWIG 在 Python 中调用 C 语言和 C++ 的接口。

SWIG 安装

1
brew install swig

C 语言的例子

C 文件

在 C 语言文件 example.c 中写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <time.h>

double My_variable = 3.0;

int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}

int my_mod(int x, int y) {
return (x%y);
}

char *get_time()
{
time_t ltime;
time(&ltime);
return ctime(&ltime);
}

SWIG 接口描述文件

SWIG 通过创建一个与 C 程序中使用声明的方式紧密匹配的接口来包装简单的 C 声明,文件为 example.i:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* example.i */
%module example
%{
/* 将头文件或函数声明放在这里,如下所示 */
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
%}

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

生成 Python 模块文件

1
swig -python example.i

生成以下两个文件:

1
2
example.py
example_wrap.c

Python 自动化编译

编写 setup.py 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from distutils.core import setup, Extension

# 生成一个扩展模块
example_module = Extension('_example' # 模块名称,必须要有下划线
,sources=['example_wrap.c' # 封装后的接口 c 文件
,'example.c'],
)

setup(name='example' # 打包后的名称
,version='0.1'
,author='SWIG Docs'
,description='Simple swig example from docs'
,ext_modules=[example_module] # 与上面的扩展模块名称一致
,py_modules=['example'] # 需要打包的模块列表
)

然后执行以下命令:

1
python setup.py build

生成一个 build 文件夹,内容如下:

1
2
3
4
5
6
7
build
|-- lib.macosx-10.9-x86_64-3.9
| |-- example.py
| |-- _example.cpython-39-darwin.so
|-- temp.macosx-10.9-x86_64-3.9
|-- example.o
|-- example_wrap.o

在 Python 中调用 C 写的模块

进入 lib 开头的文件夹,即可在 python 中 import example 模块了,调用的时候注意参数的类型需要与 C 文件中声明的一致。测试代码:

1
2
3
4
5
6
import example

print(example.get_time())
print(example.my_mod(5, 3))
print(example.fact(10))
print(example.cvar.My_variable)

结果如下:

1
2
3
4
5
Wed Feb 15 14:00:25 2023

2
3628800
3.0

C++ 的例子

C++ 文件

首先写 C++ 代码实现自己的功能接口,这里的例子是下面的 mymodule.h 和 mymodule.cpp。

  • mymodule.h 文件:
1
2
int add(int a,int b);
int sub(int a,int b);
  • mymodule.cpp 文件:
1
2
3
4
5
6
7
8
9
int add(int a, int b)
{
return a + b;
}

int sub(int a, int b)
{
return a - b;
}

SWIG 接口描述文件

编写一个 .i 的接口文件,它是 SWIG 的输入。

  • mymodule.i 文件:
1
2
3
4
5
6
7
%module mymodule
%{
#define SWIG_WITH_INIT
#include "mymodule.h"
%}

%include "mymodule.h"

其中:

  • %module 后面的名字是被封装的模块名称。封装口,python 通过这个名称加载程序。
  • %{ %} 之间添加的内容,一般包含此文件需要的一些函数声明和头文件。
  • 最后一部分声明要封装的函数和变量,直接使用 %include 文件模块头文件直接包含即可。

生成 python 模块文件

1
swig -python -c++ mymodule.i

生成以下两个文件:

1
2
mymodule.py
mymodule_wrap.cxx

Python 自动化编译

编写 setup.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from distutils.core import setup, Extension

# 生成一个扩展模块
mymodule_module = Extension('_mymodule' # 模块名称,必须要有下划线
,sources=['mymodule_wrap.cxx' # 封装后的接口 cxx 文件
,'mymodule.cpp'],
)

setup(name='mymodule' # 打包后的名称
,version='0.1'
,author='SWIG Docs'
,description='Simple swig mymodule from docs'
,ext_modules=[mymodule_module] # 与上面的扩展模块名称一致
,py_modules=['mymodule'] # 需要打包的模块列表
)

然后执行以下命令:

1
python setup.py build

生成一个 build 文件夹,内容如下:

1
2
3
4
5
6
7
build
|-- lib.macosx-10.9-x86_64-3.9
| |-- mymodule.py
| |-- _mymodule.cpython-39-darwin.so
|-- temp.macosx-10.9-x86_64-3.9
|-- mymodule.o
|-- mymodule_wrap.o

在 Python 中调用 C++ 写的模块

进入 lib 开头的文件夹,即可在 python 中 import mymodule 模块了,调用的时候注意参数的类型需要与 C++ 文件中声明的一致。测试代码:

1
2
3
4
import mymodule

print(mymodule.sub(4, 8))
print(mymodule.add(4, 8))

结果如下:

1
2
-4
12

注意事项

SWIG 接口描述文件通过 %module 命令声明模块名称;%{ %}中的所有内容将会拷贝到 SWIG 生成的 wrapper 文件,该部分被用来包含头文件、声明。SWIG 不会解析里面的内容,用户需要确保内容的正确性。

SWIG 输出的是模块所用到的 C/C++ 包裹文件。SWIG 会根据使用的语言生成附加文件。输入 file.i 文件会默认生成 file_wrap.c/file_wrap.cxx 文件。输出的 C/C++ 文件名可通过 -o 参数指定。

绝大多数 SWIG 指令是以 ‘%’ 前缀开头,用来和 C 声明做区分。SWIG 指令用于提示或改变 SWIG 的解析行为。

SWIG 能够解析大多数 C/C++ 声明,但是它没有提供全 C/C++ 解析的支持,这些限制大多数都与 C++ 高级功能有关。因此再用 C/C++ 编写要在 Python 中调用的模块时,有以下建议:

  • 避免非常规类型声明
  • 不建议直接拿 C++ 源文件,运行 SWIG 生成包裹文件。最好提供头文件给 SWIG 用于解析定义和声明。
  • 避免 C++ 的一些高级功能,例如嵌套类。

为了构建接口,SWIG 必须将 C/C++ 数据类型转换为目标语言中的等效类型。 转换过程涉及一定数量的类型强制。

SWIG 将在目标语言中与整数进行相互转换的所有 C 数据类型:

1
2
3
4
5
6
7
8
9
10
int
short
long
unsigned
signed
unsigned short
unsigned long
unsigned char
signed char
bool

SWIG 可识别以下浮点类型

1
2
float
double

SWIG 中全局变量的写法:

1
double foo;

在 Python 中,必须通过称为 cvar 的特殊变量对象访问所有全局变量。

SWIG 中常量可以使用 #define,枚举或特殊的 %constant 指令创建常量

1
2
3
4
5
6
7
8
9
10
11
#define I_CONST       5               // An integer constant
#define PI 3.14159 // A Floating point constant
#define S_CONST "hello world" // A string constant
#define NEWLINE '\n' // Character constant

enum boolean {NO=0, YES=1};
enum months {JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG,
SEP, OCT, NOV, DEC};
%constant double BLAH = 42.37;
#define PI_4 PI/4
#define FLAGS 0x04 | 0x08 | 0x40

Share