【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
-
Previous
【计算机视觉】关于`partial cross entropy loss`用于弱监督语义分割中的说明 -
Next
【python开发技术】`Boost.Python` 封装`python`接口的`C/C++`代码