【class="superseo">python开发技术】SWIG
封装python
接口的C/C++
代码
何为SWIG
?
SWIG
,全称 Simplified Wrapper and Interface Generator
,可以将C/C++
代码封装成python
、Ruby
以及Perl
等语言脚本接口。
本文主要面向python接口的封装。
SWIG
封装python
接口的C/C++
代码
整个流程说明
- 用
*.i
文件来声明所需接口; - 调用对应的
swig -python -c++ xxx.i
命令来自动生成对应的*_wrap.c
和python
文件; -
python setuptools
使用拓展模块进行编译,生成对应的库文件以及封装的上层python
使用接口。
它的好处在于,原来采用 python
提供的接口对 C/C++
代码进行封装,需要用 python
提供的 C/C++
开发库进行编写适配转化接口。而这里通过*.i
文件来声明所需接口后,所有的这些适配接口,以及一个抽象层的代码都是自动生成的。
demo
一个常规的简单C代码
下面组织结构:.c
文件和.h
文件
// example.h 声明文件
#ifndef EXAMPLE_H
#define EXAMPLE_H
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char* get_time();
#endif
// example.c 定义文件
#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(<ime);
return ctime(<ime);
}
定义了一个全局变量 My_variable
以及三个功能函数 fact
,my_mod
,get_time
。
期望的 python
接口
我们希望能够在 python
的脚本中按照下面的方式调用这些函数和全局变量:
# test.py
import example
print('My_varaiable: %s' % example.cvar.My_variable)
print('fact(5): %s' % example.fact(5))
print('my_mod(7,3): %s' % example.my_mod(7,3))
print('get_time(): %s' % example.get_time())
这里函数可以直接被调用,对应的传参为基本数据类型可以自动转换;
而全局变量则是存在 example.cvar
当中,访问的时候调用 .cvar.
。
SWIG 文件需要做些什么
需要准备一个 example.i
文件,用来声明调用接口:
%module example
%{
#include "example.h"
%}
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
说明:
-
%module
指定待访问的模块名,这里对应为example
;
除了 %module
之外,还有另外几种值得了解的类型:
-
%include
可以用来包含其它的*.i
文件,比如常用的%include "numpy.i"
和%include "std_vector.i"
。
numpy.i
: a SWIG Interface File for NumPy,是 Numpy
的 swig
文件。由于接口通常使用Numpy array
进行传参传数据,对于 swig
来讲想沟通numpy
数组与C
中指针,通过 typemap
来实现:
double rms(double* seq, int n);
def rms(seq):
"""
rms: return the root mean square of a sequence
rms(numpy.ndarray) -> double
rms(list) -> double
rms(tuple) -> double
"""
%{
#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"
这样的一个列表 (double* IN_ARRAY1, int DIM1)
被称为签名 signatures
,double* IN_ARRAY1
用来说明 double* seq
是一个输入的一维的 array
,而 int DIM1
用来说明维度大小。
类似地:
%apply (float* IN_ARRAY1, int DIM1) {(float* image, int len_image), (float* in, int len_in)}
%apply (float* INPLACE_ARRAY1, int DIM1) {(float * out, int len_out)}
对应
void bilateralfilter(float * image, int len_image, float * in, int len_in, float * out, int len_out, int H, int W, float sigmargb, float sigmaxy);
就这样类似地,将类型进行了明确。具体使用可以参考 https://docs.scipy.org/doc/numpy-1.13.0/reference/swig.interface-file.html。
std_vector.i
库提供给 C++ STL
模板库中 vector
的支持。
使用起来一个小小的 demos :
/* File : example.h */
#include <vector>
#include <algorithm>
#include <functional>
#include <numeric>
double average(std::vector<int> v) {
return std::accumulate(v.begin(),v.end(),0.0)/v.size();
}
std::vector<double> half(const std::vector<double>& v) {
std::vector<double> w(v);
for (unsigned int i=0; i<w.size(); i++)
w[i] /= 2.0;
return w;
}
void halve_in_place(std::vector<double>& v) {
std::transform(v.begin(),v.end(),v.begin(),
std::bind2nd(std::divides<double>(),2.0));
}
用SWIG进行封装:
%module example
%{
#include "example.h"
%}
%include "std_vector.i"
// Instantiate templates used by example
namespace std {
%template(IntVector) vector<int>;
%template(DoubleVector) vector<double>;
}
// Include the header file with above prototypes
%include "example.h"
使用起来:
>>> from example import *
>>> iv = IntVector(4) # Create an vector<int>
>>> for i in range(0,4):
... iv[i] = i
>>> average(iv) # Call method
1.5
>>> average([0,1,2,3]) # Call with list
1.5
>>> half([1,2,3]) # Half a list
(0.5,1.0,1.5)
>>> halve_in_place([1,2,3]) # Oops
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Type error. Expected _p_std__vectorTdouble_t
>>> dv = DoubleVector(4)
>>> for i in range(0,4):
... dv[i] = i
>>> halve_in_place(dv) # Ok
>>> for i in dv:
... print i
...
0.0
0.5
1.0
1.5
>>> dv[20] = 4.5
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "example.py", line 81, in __setitem__
def __setitem__(*args): return apply(examplec.DoubleVector___setitem__,args)
IndexError: vector index out of range
%{ %}
用来指定在example_wrap.c
文件中所需要的声明,这里直接用example.h
文件来代替,换成example.h
中所声明的四个内容也是一样的;- 剩下的四行,即为声明所需要的对外暴露的四个接口;
- 除了
%
部分的内容,其它均遵循h
文件的语法;
这些完毕之后,直接使用 swig -python example.i
,在 example.i
所在目录也会生成两个文件:example_wrap.c
和 example.py
。
编译 example_wrap.c
这里因为直接使用python脚本,那么直接使用setuptools来编写编译脚本,省去编写Makefile的步骤,即可:
from distutils.core import setup, Extension
import numpy
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
example_module = Extension('_example',
sources=['example_wrap.c',
'example.c',],
extra_compile_args=["-fopenmp"],
include_dirs=[numpy_include]
)
setup(name='example',
version="0.1",
author="ZhangPY",
description="Simple swig ext from docs",
ext_modules=[example_module],
py_modules=['example']
)
注意:
-
Extension
的名称为_example
,因为生成的 example.py 为更进一步的封装调用,对_example
进行的调用。所以python setup.py build_ext
会在build
目录下生成对应的_example.cp36-win_amd64.pyd
。将其与example.py
放置在一起即可使用example.py
。
参考文档
http://www.swig.org/Doc3.0/Python.html
20200412