用python指令碼寫應用比較方便,本文我們將學慣用c/c++python擴充的執行個體。
說明: 測試環境中作業系統為CentOS6.5_x64,python版本為2.6
直接調用動態庫
1、編寫模組動態庫文代碼
這裡以求最大數為樣本
代碼(callTest1.cpp)如下:
extern "C"
{
int Max(int i1,int i2)
{
return (i1>i2)?i1:i2;
}
}
在bash中執行以下命令:
g++ -fPIC -shared -o libcallTest1.so callTest1.cpp
產生動態庫檔案libcallTest1.so
2、使用python調用動態庫
可以通過ctypes調用動態庫檔案,具體如下:
#! /usr/bin/env python
from ctypes import *
import os
so1 = CDLL(os.getcwd() + '/libcallTest1.so')
print so1.Max(1,3)
使用boost庫擴充python
這種方式可以直接產生python模組,使用import操作直接匯入即可。 當然使用這種方式,需要安裝boost庫,具體操作這裡就不說了,不懂的朋友自己查下。
boost官網:http://www.boost.org/
1、模組代碼如下:
檔案名稱:boostCallTest1.cpp
檔案內容:
int Max(int i1,int i2)
{
return (i1>i2)?i1:i2;
}
#include
#include
using namespace boost::python;
BOOST_PYTHON_MODULE(boostCallTest1)
{
def("Max",Max);
}
2、編寫模組編譯指令碼
檔案名稱: setup.py
檔案內容:
#!/usr/bin/env python
from distutils.core import setup
from distutils.extension import Extension
setup(name="PackageName",
ext_modules=[
Extension("boostCallTest1", ["boostCallTest1.cpp"],
libraries = ["boost_python"])
])
3、編譯模組並測試
編譯模組:
python setup.py build
會在build目錄產生boostCallTest1.so檔案,進入該目錄,可如下使用:
>>> import boostCallTest1
>>> boostCallTest1.Max(1,3)
3
使用swig擴充python
SWIG是個協助使用C或者C++編寫的軟體能與其它各種進階程式設計語言進行嵌入聯結的開發工具。
SWIG能應用於各種不同類型的語言套件括常用指令碼編譯語言例如Perl, PHP, Python, Tcl, Ruby and PHP。
swig官網: http://www.swig.org/download.html
可以通過yum直接安裝:
yum install swig
1、編寫程式碼
檔案名稱:swigCall.cpp
內容如下:
int Max(int i1,int i2)
{
return (i1>i2)?i1:i2;
}
2、編寫介面檔案
檔案名稱:swigCall.i
內容如下:
%module swigCall
%{
extern int Max(int i1,int i2);
%}
extern int Max(int i1,int i2);
3、編寫Makefile檔案
內容如下:
all:
make swigCall
swigCall:
swig -python -c++ swigCall.i
g++ -c swigCall.cpp swigCall_wrap.cxx -I/usr/include/python2.6/ -fPIC
g++ -shared swigCall.o swigCall_wrap.o -o _swigCall.so
clean:
rm -f swigCall_wrap.c*
rm -f *.py*
rm -f *.so
rm -f *.o
注意:swig命令中要使用-c++參數編譯c++代碼
使用SIP擴充python
SIP是從SWIG發展而來,專為Python調用C/C++模組使用的。 注意,這裡的SIP和voip中的sip不是同一個東西,這裡的sip是擴充python用的,voip中的sip是一個通訊協定,不要搞混了。
注意:
需要安裝python sip庫;
1、編寫c++模組
1.1 編寫標頭檔
檔案名稱: sipCall.h 檔案內容:
class TestMode
{
public:
int Max(int i1,int i2);
};
1.2 編寫模組內容
檔案名稱:sipCall.cpp 檔案內容:
#include "sipCall.h"
int TestMode::Max(int i1,int i2)
{
return (i1>i2)?i1:i2;
}
2、編寫介面檔案
檔案名稱:sipCall.sip 檔案內容:
%Module TestMode
class TestMode {
%TypeHeaderCode
#include "sipCall.h"
%End
public:
int Max(int i1,int i2);
};
3、生存靜態庫
這裡用指令碼實現,檔案名稱:genlib.sh
檔案內容:
#! /bin/bash
g++ -c -fPIC sipCall.cpp
ar -crs libsipCall.a sipCall.o
4、編寫configure檔案
該指令碼用於產生Makefile,內容如下:
#! /usr/bin/env python
import os
import sipconfig
build_file = "sipCall.sbf"
config = sipconfig.Configuration()
cmd = " ".join([config.sip_bin, "-c", ".", "-b", build_file, "sipCall.sip"])
os.system(cmd)
makefile = sipconfig.SIPModuleMakefile(config, build_file)
makefile.extra_libs = ["sipCall"]
makefile.LIBDIR.append(".")
makefile.generate()
5、運行genlib.sh指令碼產生lib檔案;
6、運行configure.py指令碼產生Makefile;
7、運行make產生模組檔案(so檔案);
8、python測試如下:
>>> import TestMode
>>> dir(TestMode)
['TestMode', '__doc__', '__file__', '__name__', '__package__']
>>> s = TestMode.TestMode()
>>> s.Max(1,3)
3
本文github地址:
https://github.com/mike-zhang/mikeBlogEssays/blob/master/2015/20150808_用c++擴充python.md
Python的C/C++擴充
可擴充性是Python的一大特色,一方面,由於Python是解釋執行的,這導致運行速度會比編譯型語言慢,因此可以通過使用C/C++重寫核心部分代碼以解決效能上的瓶頸(程式90%的時間再運行10%的代碼);另一方面,可以通過擴充,達到添加整合一些額外的功能以及保持專有原始碼的目的。在本文接下來的部分中我們將討論如何編寫C/C++擴充代碼,並使用它們的功能。
我們要建立的是一個可以在Python內啟動並執行C/C++模組,因此需要解決如何使C代碼和Python代碼能夠進行互動以及資料共用。擴充是通過為C代碼編寫封裝函數(類似適配器)實現雙向互動和資料共用的。
一.一般的封裝模式
每一個封裝函數主要做三件事:
1.把輸入的Python對象轉換為C/C++對象;
2.調用C/C++函數;
3.轉換C/C++函數處理的輸出結果為Python對象,並返回;
先用一個簡單範例描述模組大體的過程:
//wrap.cpp//1.C代碼#include "Python.h"int add(int arg1, int arg2){ return arg1 + arg2;}//2.add的封裝函數:static PyObject* wrap_add(PyObject *self, PyObject *args){ //把輸入的Python對象轉換為C/C++能識別的資料 int arg1, arg2; if(!PyArg_ParseTuple(args, "ii", &arg1, &arg2)) return NULL; //調用C/C++函數,得到結果 int result = add(arg1,arg2); //把得到的結果封裝成Python對象,並返回 return (PyObject*)Py_BuildValue("i", result);}//3.為模組添加PyMethodDef方法數組static PyMethodDef wrap_methods[] ={ {"add", wrap_add, METH_VARARGS}, {NULL, NULL}};//4.增加模組初始化函數InitModulePyMODINIT_FUNC initwrap (void){ Py_InitModule("wrap ", wrap_methods);}
把上面的代碼編譯,產生wrap.pyd。
啟動控制台切換到相應的工程目錄,即可測試產生的模組:
(PS:似乎只有Release模式產生的模組才能正常運行)
相關說明
每個封裝函數都有如下形式:
PyObject * wrap_function(PyObject *, PyObject * args)
函數第一個參數,有特殊用途,通常選擇忽略。第二個參數是一個PyTuple(PyObject的子類型,和Python中的Tuple對應),是調用時Python傳入的參數。
函數PyArg_ParseTuple把Python對象轉換為C的資料類型,其聲明如下:
int PyArg_ParseTuple(PyObject* args, char* format, ...);
參數args必須是一個tuple對象,包含傳遞過來的參數, format 參數必須是格式化字串。剩餘參數是各個變數的地址,類型要與格式化字串對應。如:
int arg1, arg2;
PyArg_ParseTuple(args, "ii", &arg1, &arg2);
函數Py_BuildValue可以說是PyArg_ParseTuple的逆過程,它把C的資料類型封裝為Python對象。
return (PyObject*)Py_BuildValue("i", result);
把調用C函數的結果result封裝為Python的int對象,並返回。
static PyMethodDef wrap_methods[] ={
{"add", wrap_add, METH_VARARGS},
{NULL, NULL}
};
這個數組包含多個數組,其中的每個數組都包含了一個函數的資訊,以便解譯器能夠匯入並調用它們,最後一個NULL數組表示列表的結束。METH_VARARGS常量表示參數以元組形式傳入。
PyMODINIT_FUNC initwrap (void)
{
Py_InitModule("wrap ", wrap_methods);
}
模組初始化函數void initModuleName(),這部分代碼在模組被匯入的時候被解譯器調用。這樣所有的封裝就已經完成了。
二.C++類的封裝
// Example.cppclass Numbers{public: Numbers(int first, double second) : m_first( first), m_second(second){} double NumMemberMult(void){ return m_first*m_second;}private: int m_first; double m_second;};static void PyDelNumbers(void *ptr){ Numbers * oldnum = static_cast(ptr); delete oldnum; return;}PyObject *Example_new_Numbers(PyObject *, PyObject* args){ int arg1; double arg2; int ok = PyArg_ParseTuple(args,"id",&arg1,&arg2); if(!ok) return NULL; //動態建立一個新對象 Numbers *newnum = new Numbers(arg1, arg2); //把指標newnum封裝成PyCObject對象並返回給解譯器 return PyCObject_FromVoidPtr( newnum, PyDelNumbers);}PyObject * Example_Numbers_MemberMult(PyObject *, PyObject* args){ PyObject *pynum = 0; int ok = PyArg_ParseTuple( args, "O", &pynum); if(!ok) return NULL; //把PyCObject轉換為void指標 void * temp = PyCObject_AsVoidPtr(pynum); //把void指標轉換為一個Numbers對象指標 Numbers * thisnum = static_cast(temp); //調用函數 double result = thisnum->NumMemberMult(); //返回結果 return Py_BuildValue("d",result);}static PyMethodDef Example_methods[] = { {"Numbers", Example_new_Numbers, METH_VARARGS}, {"NumMemberMult", Example_Numbers_MemberMult, METH_VARARGS}, {NULL, NULL}};PyMODINIT_FUNC initExample (void){ Py_InitModule("Example", Example_methods);}
C++類的封裝和C函數的封裝大同小異,因為對類的封裝是對函數的封裝,所以仍需要用Python代碼對擴充模組進行封裝,才可以像類一樣的使用。
#example.pyfrom Example import *class example(object): def __init__(self,arg1,arg2): self._base = Numbers(arg1,arg2) def MemberMult(self): return NumMemberMult(self._base)
這樣C++類的封裝也完成了。
三.C/C++中建立Python list
static PyObject* Windy_dict(PyObject *self, PyObject *args){ //建立列表 PyObject *newlist = PyList_New(0); PyList_Append(newlist, PyString_FromString("first")); PyList_Append(newlist, PyString_FromString("second")); PyList_Append(newlist, PyString_FromString("third")); //返回給解譯器 return newlist;}
建立其它Python對象也類似list的建立,返回給解譯器的都是一個對象指標。C/C++對Python對象的解析差不多是建立時的逆過程。具體的物件模型及API可以查閱相關參考文檔。