?
原文鏈接
作者
一、問題
? ????Python模塊和C/C++的動態庫間相互調用在實際的應用中會有所涉及,在此作一總結。
二、Python調用C/C++
1、Python調用C動態鏈接庫
??????? Python調用C庫比較簡單,不經過任何封裝打包成so,再使用python的ctypes調用即可。
(1)C語言文件:pycall.c
/***gcc -o libpycall.so -shared -fPIC pycall.c*/ #include#include int foo(int a, int b) { printf("you input %d and %d\n", a, b); return a+b; }
(2)gcc編譯生成動態庫libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++編譯生成C動態庫的代碼中的函數或者方法時,需要使用extern "C"來進行編譯。
(3)Python調用動態庫的文件:pycall.pyimport ctypes ll = ctypes.cdll.LoadLibrary lib = ll("./libpycall.so") lib.foo(1, 3) print '***finish***'
(4)運行結果:
2、Python調用C++(類)動態鏈接庫?
?????? 需要extern "C"來輔助,也就是說還是只能調用C函數,不能直接調用方法,但是能解析C++方法。不是用extern "C",構建后的動態鏈接庫沒有這些函數的符號表。(1)C++類文件:pycallclass.cpp
3、Python調用C/C++可執行程序
(1)C/C++程序:main.cppimport commands import os main = "./testmain" if os.path.exists(main): rc, out = commands.getstatusoutput(main) print 'rc = %d, \nout = %s' % (rc, out) print '*'*10 f = os.popen(main) data = f.readlines() f.close() print data print '*'*10 os.system(main)
(4)運行結果:
?
4、擴展Python(C++為Python編寫擴展模塊)
?????? 所有能被整合或導入到其它python腳本的代碼,都可以被稱為擴展。可以用Python來寫擴展,也可以用C和C++之類的編譯型的語言來寫擴展。Python在設計之初就考慮到要讓模塊的導入機制足夠抽象。抽象到讓使用模塊的代碼無法了解到模塊的具體實現細節。Python的可擴展性具有的優點:方便為語言增加新功能、具有可定制性、代碼可以實現復用等。?????? 為 Python 創建擴展需要三個主要的步驟:創建應用程序代碼、利用樣板來包裝代碼和編譯與測試。
(1)創建應用程序代碼
#include#include #include int fac(int n) { if (n < 2) return(1); /* 0! == 1! == 1 */ return (n)*fac(n-1); /* n! == n*(n-1)! */ } char *reverse(char *s) { register char t, /* tmp */ *p = s, /* fwd */ *q = (s + (strlen(s) - 1)); /* bwd */ while (p < q) /* if p < q */ { t = *p; /* swap & move ptrs */ *p++ = *q; *q-- = t; } return(s); } int main() { char s[BUFSIZ]; printf("4! == %d\n", fac(4)); printf("8! == %d\n", fac(8)); printf("12! == %d\n", fac(12)); strcpy(s, "abcdef"); printf("reversing 'abcdef', we get '%s'\n", \ reverse(s)); strcpy(s, "madam"); printf("reversing 'madam', we get '%s'\n", \ reverse(s)); return 0; }
? ? ? ?上述代碼中有兩個函數,一個是遞歸求階乘的函數fac();另一個reverse()函數實現了一個簡單的字符串反轉算法,其主要目的是修改傳入的字符串,使其內容完全反轉,但不需要申請內存后反著復制的方法。
(2)用樣板來包裝代碼??????? 接口的代碼被稱為“樣板”代碼,它是應用程序代碼與Python解釋器之間進行交互所必不可少的一部分。樣板主要分為4步:a、包含Python的頭文件;b、為每個模塊的每一個函數增加一個型如PyObject* Module_func()的包裝函數;c、為每個模塊增加一個型如PyMethodDef ModuleMethods[]的數組;d、增加模塊初始化函數void initModule()。
???????? 從Python到C的轉換用PyArg_Parse*系列函數,int PyArg_ParseTuple():把Python傳過來的參數轉為C;int PyArg_ParseTupleAndKeywords()與PyArg_ParseTuple()作用相同,但是同時解析關鍵字參數;它們的用法跟C的sscanf函數很像,都接受一個字符串流,并根據一個指定的格式字符串進行解析,把結果放入到相應的指針所指的變量中去,它們的返回值為1表示解析成功,返回值為0表示失敗。從C到Python的轉換函數是PyObject* Py_BuildValue():把C的數據轉為Python的一個對象或一組對象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的參數按格式字符串所指定的格式轉換成一個Python的對象。
??????? C與Python之間數據轉換的轉換代碼:
??????? 為每個模塊增加一個型如PyMethodDef ModuleMethods[]的數組,以便于Python解釋器能夠導入并調用它們,每一個數組都包含了函數在Python中的名字,相應的包裝函數的名字以及一個METH_VARARGS常量,METH_VARARGS表示參數以tuple形式傳入。 若需要使用PyArg_ParseTupleAndKeywords()函數來分析命名參數的話,還需要讓這個標志常量與METH_KEYWORDS常量進行邏輯與運算常量 。數組最后用兩個NULL來表示函數信息列表的結束。
???????? 所有工作的最后一部分就是模塊的初始化函數,調用Py_InitModule()函數,并把模塊名和ModuleMethods[]數組的名字傳遞進去,以便于解釋器能正確的調用模塊中的函數。
(3)編譯
??????? 為了讓新Python的擴展能被創建,需要把它們與Python庫放在一起編譯,distutils包被用來編譯、安裝和分發這些模塊、擴展和包。
??????? 創建一個setup.py 文件,編譯最主要的工作由setup()函數來完成:
???????? 測試模塊:
(5)引用計數和線程安全
???? Python對象引用計數的宏:Py_INCREF(obj)增加對象obj的引用計數,Py_DECREF(obj)減少對象obj的引用計數。Py_INCREF()和Py_DECREF()兩個函數也有一個先檢查對象是否為空的版本,分別為Py_XINCREF()和Py_XDECREF()。
????? 編譯擴展的程序員必須要注意,代碼有可能會被運行在一個多線程的Python環境中。這些線程使用了兩個C宏Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS,通過將代碼和線程隔離,保證了運行和非運行時的安全性,由這些宏包裹的代碼將會允許其他線程的運行。
?
三、C/C++調用Python
?????? C++可以調用Python腳本,那么就可以寫一些Python的腳本接口供C++調用了,至少可以把Python當成文本形式的動態鏈接庫,?
需要的時候還可以改一改,只要不改變接口。缺點是C++的程序一旦編譯好了,再改就沒那么方便了。
(1)Python腳本:pytest.py
#test function def add(a,b): print "in python function add" print "a = " + str(a) print "b = " + str(b) print "ret = " + str(a+b) return def foo(a): print "in python function foo" print "a = " + str(a) print "ret = " + str(a * a) return class guestlist: def __init__(self): print "aaaa" def p(): print "bbbbb" def __getitem__(self, id): return "ccccc" def update(): guest = guestlist() print guest['aa'] #update()
(2)C++代碼:
/**g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6**/ #includeint main(int argc, char** argv) { // 初始化Python //在使用Python系統前,必須使用Py_Initialize對其 //進行初始化。它會載入Python的內建模塊并添加系統路 //徑到模塊搜索路徑中。這個函數沒有返回值,檢查系統 //是否初始化成功需要使用Py_IsInitialized。 Py_Initialize(); // 檢查初始化是否成功 if ( !Py_IsInitialized() ) { return -1; } // 添加當前路徑 //把輸入的字符串作為Python代碼直接運行,返回0 //表示成功,-1表示有錯。大多時候錯誤都是因為字符串 //中有語法錯誤。 PyRun_SimpleString("import sys"); PyRun_SimpleString("print '---import sys---'"); PyRun_SimpleString("sys.path.append('./')"); PyObject *pName,*pModule,*pDict,*pFunc,*pArgs; // 載入名為pytest的腳本 pName = PyString_FromString("pytest"); pModule = PyImport_Import(pName); if ( !pModule ) { printf("can't find pytest.py"); getchar(); return -1; } pDict = PyModule_GetDict(pModule); if ( !pDict ) { return -1; } // 找出函數名為add的函數 printf("----------------------\n"); pFunc = PyDict_GetItemString(pDict, "add"); if ( !pFunc || !PyCallable_Check(pFunc) ) { printf("can't find function [add]"); getchar(); return -1; } // 參數進棧 *pArgs; pArgs = PyTuple_New(2); // PyObject* Py_BuildValue(char *format, ...) // 把C++的變量轉換成一個Python對象。當需要從 // C++傳遞變量到Python時,就會使用這個函數。此函數 // 有點類似C的printf,但格式不同。常用的格式有 // s 表示字符串, // i 表示整型變量, // f 表示浮點數, // O 表示一個Python對象。 PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3)); PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4)); // 調用Python函數 PyObject_CallObject(pFunc, pArgs); //下面這段是查找函數foo 并執行foo printf("----------------------\n"); pFunc = PyDict_GetItemString(pDict, "foo"); if ( !pFunc || !PyCallable_Check(pFunc) ) { printf("can't find function [foo]"); getchar(); return -1; } pArgs = PyTuple_New(1); PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",2)); PyObject_CallObject(pFunc, pArgs); printf("----------------------\n"); pFunc = PyDict_GetItemString(pDict, "update"); if ( !pFunc || !PyCallable_Check(pFunc) ) { printf("can't find function [update]"); getchar(); return -1; } pArgs = PyTuple_New(0); PyTuple_SetItem(pArgs, 0, Py_BuildValue("")); PyObject_CallObject(pFunc, pArgs); Py_DECREF(pName); Py_DECREF(pArgs); Py_DECREF(pModule); // 關閉Python Py_Finalize(); return 0; }
(3)C++編譯成二進制可執行文件:g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6,編譯選項需要手動指定Python的include路徑和鏈接接路徑(Python版本號根據具體情況而定)。
(4)運行結果:?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
