初識代碼封裝工具SWIG(回調Python函數)

這不是我最先使用swig了,以前在寫Kynetix的時候就使用了swig爲python封裝了C語言寫的擴展模塊。可是當時我對C++還不是很瞭解,對其中的一些概念也只是拿來直接用,沒有理解究竟是什麼,爲何會有這種功能。因此昨天我又拿出了《python科學計算》這本書來溫習了一下swig那一部分,果真對swig又有了新的認識。java

對swig真正全的使用都在swig的文檔中有詳細的介紹,並且因爲swig支持不少種語言,例如java、python、Tcl等,所以這份文檔內容至關的豐富。因爲如今尚未很好的中文資源,因此如今只能默默的看英文文檔了。python

SWIG用來作什麼?

swig是個封裝器,它讀取C/C++的函數或者類聲明,並將這些函數或者類進行封裝,生成一個封裝代碼,其中包含一個目標語言的文件供目標文件調用,還包含一個C/C++的封裝文件以<source>_wrap.c或者<source>_wrap.cxx命名。這個生成的_wrap.cxx文件就是個封裝文件,而後咱們將這個封裝文件同咱們本身的C/C++代碼或者是已經編譯好的目標文件(*.o或者*.obj文件)或者庫文件(*.lib/*.a文件)一塊兒編譯並鏈接,就會生成一個咱們目標語言可以認識而且調用的模塊。c++

其中的swig生成的封裝代碼的做用就是可以使得python與C/C++之間可以無阻礙的溝通,也就是ruby

  • 將封裝函數接收到的Python對象轉換成C/C++可以處理的數據
  • 有了數據之後便執行C/C++的函數
  • 執行C/C++函數將返回值在轉換成python對象返回給python代碼去處理

簡單的操做

swig須要一個*.i文件,也就是swig接口文件(interface)告訴swig須要如何處理C/C++的數據、函數和類。函數

  1. 先生成封裝代碼spa

    1
    $ swig -c++ -python demo.i

    這個命令就會生成python的封裝代碼,當前路徑下會出現demo.pydemo_wrap.cxx文件。其中demo.py文件中是python代碼,也就是一個殼子,他可以讓python程序調用這個模塊中的函數,可是函數的實體並不在裏面,由於函數的實體是C/C++編譯後的動態庫文件。指針

  2. 編譯封裝代碼
    這一步只說明生成的C/C++的封裝代碼是能夠單獨編譯的,這就將封裝同C/C++庫分割開,我按照個人方式寫C/C++代碼不用管封裝的事情,最後只要把wrap的目標文件連接起來就行了。code

    1
    $ g++ -fPIC -c demo_wrap.cxx

    須要注意的是-fPIC這個參數是必定要加的,否則就沒法生成動態連接庫。具體這個參數是作什麼的,顧名思義就是生成一個位置獨立的代碼段,這樣不管函數在哪都可以動態的調用這個動態庫了,詳見:http://stackoverflow.com/questions/5311515/gcc-fpic-option對象

  3. 連接成擴展模塊
    將封裝文件與庫文件連接成爲python可以調用的動態庫,這一步就好像是使用wrap這個文件給C/C++庫文件進行化妝,化成python認識的那種樣子。固然swig也能夠根據使用者的需求把C/C++的庫化妝成其餘語言認識的樣子如java、ruby等。
    這樣就會生成一個_demo.so或者_demo.pyd的庫文件,python能夠經過以前的demo.py或者直接_demo.pyd來用本身的方式調用C/C++的函數和類來爲本身服務。blog

    1
    $ g++ -shared demo_wrap .o demo.c -o _demo.so

關於類型映射

.i文件描述瞭如何建立封裝文件,具體的語法我不在這裏總結了能夠直接去看文檔。其中比較重要的部分就是如何讓python和C/C++進行交流,好比如何處理python沒有指針操做與C/C++傳入指針的矛盾,如何處理python返回多個值與C/C++只能返回一個值的矛盾等。

類型映射就是一套規則,告訴swig如何將這些矛盾化解,並定義名稱參數,將名稱參數寫道接口文件的類和函數聲明中,讓swig處理。
SWIG已經有了默認的一些類型映射,例如* OUTPUT* INPUT* INOUT等來告訴swig這些參數處理成python接口時候怎麼處理。
例如若是我在接口文件中聲明瞭一個C函數

1
void add_multi(double x, double y, double * OUTPUT, double * OUTPUT);

 

這時候swig就認出了OUTPUT是一種類型映射定義的名稱參數,C語言修改這兩個指針指向的值要處理成python調用這個函數返回這兩個指針指向的值的list。

除了使用已定義的類型映射,swig還支持自定義的類型映射,這裏我也很少講了,之後若是在寫類型映射的時候我會更新。

回調Python函數

這裏主要是可以讓C/C++代碼調用python的函數。如今必需要理解這一點,由於KMCLib中就用到了這個,使得可以讓用戶使用python自定義RateCalculator而後從新定義C++的虛函數,是C++程序可以調用python類的方法。
我在這裏舉一個例子,就是在python中可以繼承C++中定義的類,而且在python中從新定義C++類的虛函數。

  1. 開啓此功能,在接口文件中的模塊名稱定義中要加入director參數

    1
    % module(director="1") demo
  2. 在但願可以調用python函數的C++類中,經過%feature指令開啓director功能:

    1
    %feature( "director") Sum;

我下面把別人的例子貼上來,方便之後本身回憶:

定義一個求和類:

1
2
3
4
5
6
7
8
9
10
11
class Sum
{
public:
Sum() {};
 
~Sum() {};
 
double Cal(int start, int end); // 從start開始到end結束,將Func做用於中間的整數而後球和。
 
virtual double Func(double x) { return x }; // python 中的Sum類的子類能夠重寫這個虛函數
}

 

在接口文件中咱們放入此類的聲明的時候開啓」director」功能:

1
2
3
4
5
6
7
8
9
10
11
%feature( "director") Sum
{
public:
Sum() {};
 
~Sum() {};
 
double Cal(int start, int end);
 
virtual double Func(double x) { return x };
}

 

這樣在python中咱們就能夠這麼用了:

1
2
3
4
5
import demo
 
class SumReciprocal(demo.Sum): # Sum類的派生類
def Func(self, x): # 重寫Sum的Func虛方法
return 1/x

 

而後咱們就能夠直接在python中使用這個重寫過Sum類方法的子類了。

總結

swig很強大,可以熟練使用,是快速並且方便獨立的構建python語言以及其餘動態語言的擴展模塊,真是感受我站在了巨人的肩膀上了。

http://pytlab.org/2016/04/02/%E5%88%9D%E8%AF%86%E4%BB%A3%E7%A0%81%E5%B0%81%E8%A3%85%E5%B7%A5%E5%85%B7SWIG/