【python開發技術】`SWIG` 封裝`python`接口的`C/C++`代碼

【python開發技術】SWIG 封裝python接口的C/C++代碼

何爲SWIG

SWIG,全稱 Simplified Wrapper and Interface Generator,可以將C/C++代碼封裝成pythonRuby以及Perl等語言腳本接口。

本文主要面向python接口的封裝。

SWIG 封裝python接口的C/C++代碼

整個流程說明

  • *.i文件來聲明所需接口;
  • 調用對應的 swig -python -c++ xxx.i 命令來自動生成對應的 *_wrap.cpython 文件;
  • 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(&ltime);
  return ctime(&ltime);
}

定義了一個全局變量 My_variable 以及三個功能函數 factmy_modget_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();

說明:

  1. %module 指定待訪問的模塊名,這裏對應爲 example;

除了 %module 之外,還有另外幾種值得了解的類型:

  • %include 可以用來包含其它的 *.i 文件,比如常用的 %include "numpy.i"%include "std_vector.i"

numpy.i: a SWIG Interface File for NumPy,是 Numpyswig 文件。由於接口通常使用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)被稱爲簽名 signaturesdouble* 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
  1. %{ %} 用來指定在 example_wrap.c 文件中所需要的聲明,這裏直接用 example.h 文件來代替,換成 example.h 中所聲明的四個內容也是一樣的;

  2. 剩下的四行,即爲聲明所需要的對外暴露的四個接口;

  3. 除了 % 部分的內容,其它均遵循 h 文件的語法;

這些完畢之後,直接使用 swig -python example.i,在 example.i 所在目錄也會生成兩個文件:example_wrap.cexample.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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章