17 7月 2013

[Python] Windows底下 使用SWIG呼叫C/C++的function

使用Python的script language的特性,開發起來實在是又快又舒服,但是Python有個致命的缺點就是GIL,在Multi-Thread的機制下,有了這個GIL的限制,感覺Multi-Thread就被俺掉一半了。照理來說heavy的blocking code應該要寫在另一條thread,並且放掉GIL,讓其他的thread有時間去做事才對。透過Python轉call進C/C++的function後,才有機會讓heavy的function放掉Python的GIL。

不過,本篇的重點在於介紹如果讓Python呼叫C/C++ level的function,有機會的話,再介紹一下GIL好了。目前其實有很多Tool提供這樣的功能,包括SWIG、Boost.Python、Robin…等等,這邊就介紹一下SWIG的用法。


先簡單說明一下SWIG是怎麼辦到的
1. 將我們C/C++ level的code包在一個"Sample" DLL project
2. 寫一份interface的Sample.i檔給SWIG.exe parse後產生出一個Sample_wrap.cSample.py
3. 我們再將Sample_wrap.c加進DLL project裡,一起compile成_Sample.pyd
4. 在Python code中import Sample 就能呼叫Sample裡的API了
所以,其實SWIG做的事情只是parse Sample.i檔,產生wrapper檔:Sample_wrap.c,DLL project連著這個wrapper檔一起compile的話,就可以讓Python層呼叫執行了。


1. 下載SWIG

目前最新的SWIG已經2.0.10版了,下載後,解壓縮,我們在compile project時,只需要其中的swig.exe

2. 設定VC的Include, Library, Execute path

為了在C/C++ code裡撰寫CPython,必須指定Python的Include, Library path
而指定Execute path是為了在Project property使用swig時,不需要輸入絕對路徑

假如電腦裡安裝的是Python 2.7
在Window裡的預設路行就是C:\Python27
Tool | Options | Projects and Solutions | VC++ Directories裡新增
  • Include files : C:\Python27\include
  • Library files : C:\Python27\libs
  • Executable files : C:\swigwin-2.0.10\ (第一步中解壓縮的路徑)

3. 建立C/C++ DLL Project

我以VC2008(其他版本也可以),建立一個Win32的C/C++的DLL Project

選擇"Emtpy Project"
若不選擇"Empty Project"也沒關係,記得要將 Project Property | C/C++ | Precompiled Headers | Create/Use Precompiled Header 切換成 "Not Using Precompiled Headers"

4. 撰寫C/C++的API - Sample.h, Sample.cpp

Sample.h
#include "Python.h"

int AddOne(int n);
int Sqrt(int n);
PyObject* SqrtInPyObj(PyObject* obj);


Sample.cpp
#include "Sample.h"

int AddOne(int n)
{
    return n+1;
}

int Sqrt(int n)
{
    return n*n;
}

PyObject* SqrtInPyObj(PyObject* obj)
{
    int n = PyInt_AsLong(obj);
    return Py_BuildValue("i", n*n);
}


5. 設置interface檔 - Sample.i

由於SWIG需要此interface檔來產生wrapper的cxx檔,因此需撰寫一個 Sample.i 檔加到Project裡,這樣才能在Compile後即時update最新的pyd檔
右鍵選擇 Sample.i 的Property,設置Custom Build Step

Example : c, output file為Sample_wrap.c

  • Command Line
    • swig.exe -python -o $(ProjectDir)\$(InputName)_wrap.c "$(InputPath)"
  • Outputs
    • $(InputName)_wrap.c


Example : c++, output file為Sample_wrap.cxx, 記得加上-c++

  • Command Line
    • swig.exe -c++ -python -o $(ProjectDir)\$(InputName)_wrap.cxx "$(InputPath)"
  • Outputs
    • $(InputName)_wrap.cxx


Sample.i範例
%module Sample 

%{
#include "Sample.h"
%}

%include "Sample.h"
interface檔尚有許多的功能,有興趣的人可以再深入研究 - SWIG 2.0 Python Document
這邊就不多做著墨了

6. 加入Sample_wrap.cxx or Sample_wrap.c至Project中

在做完以上的步驟後,可以先compile一次,就會透過 Sample.i 的Custom Build Step產生Sample_wrap.cxx檔,再將此當加入Project中一起Compile

7. Compile

點選Project Property,修改
Linker | Output File 
$(OutDir)\_$(ProjectName).pyd
讓最後的檔案輸出成_Sample.pyd

點選Project Property,修改
C/C++ | Preprocessor | Preprocessor Definitions 
新增 __WIN32__

以上都完成後,就可以開始Compile了

8. Import Python module

將Compile過後的Sample.py_Sample.pyd複製到你的Python script旁,執行應該就可以看到結果了!
Example :
import Sample
print Sample.AddOne(1) # >>> 2
print Sample.Sqrt(2) # >>> 4
print Sample.Sqrt(3) # >>> 9
print Sample.SqrtInPyObj(2) # >>> 4
print Sample.SqrtInPyObj(3) # >>> 9


沒有留言: