【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