Python與C/C++的相互調(diào)用案例
一、問題
Python模塊和C/C++的動態(tài)庫間相互調(diào)用在實(shí)際的應(yīng)用中會有所涉及,在此作一總結(jié)。
二、Python調(diào)用C/C++
1、Python調(diào)用C動態(tài)鏈接庫
Python調(diào)用C庫比較簡單,不經(jīng)過任何封裝打包成so,再使用python的ctypes調(diào)用即可。
(1)C語言文件:pycall.c
/***gcc -o libpycall.so -shared -fPIC pycall.c*/ #include <stdio.h> #include <stdlib.h> int foo(int a, int b) { printf("you input %d and %d\n", a, b); return a+b; }
(2)gcc編譯生成動態(tài)庫libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++編譯生成C動態(tài)庫的代碼中的函數(shù)或者方法時,需要使用extern "C"來進(jìn)行編譯。
(3)Python調(diào)用動態(tài)庫的文件:pycall.py
import ctypes ll = ctypes.cdll.LoadLibrary lib = ll("./libpycall.so") lib.foo(1, 3) print '***finish***'
(4)運(yùn)行結(jié)果:
2、Python調(diào)用C++(類)動態(tài)鏈接庫
需要extern "C"來輔助,也就是說還是只能調(diào)用C函數(shù),不能直接調(diào)用方法,但是能解析C++方法。不是用extern "C",構(gòu)建后的動態(tài)鏈接庫沒有這些函數(shù)的符號表。
(1)C++類文件:pycallclass.cpp
#include <iostream> using namespace std; class TestLib { public: void display(); void display(int a); }; void TestLib::display() { cout<<"First display"<<endl; } void TestLib::display(int a) { cout<<"Second display:"<<a<<endl; } extern "C" { TestLib obj; void display() { obj.display(); } void display_int() { obj.display(2); } }
(2)g++編譯生成動態(tài)庫libpycall.so:g++ -o libpycallclass.so -shared -fPIC pycallclass.cpp。
(3)Python調(diào)用動態(tài)庫的文件:pycallclass.py
import ctypes so = ctypes.cdll.LoadLibrary lib = so("./libpycallclass.so") print 'display()' lib.display() print 'display(100)' lib.display_int(100)
(4)運(yùn)行結(jié)果:
3、Python調(diào)用C/C++可執(zhí)行程序
(1)C/C++程序:main.cpp
#include <iostream> using namespace std; int test() { int a = 10, b = 5; return a+b; } int main() { cout<<"---begin---"<<endl; int num = test(); cout<<"num="<<num<<endl; cout<<"---end---"<<endl; }
(2)編譯成二進(jìn)制可執(zhí)行文件:g++ -o testmain main.cpp。
(3) Python調(diào)用程序:main.py
import 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)運(yùn)行結(jié)果:
4、擴(kuò)展Python(C++為Python編寫擴(kuò)展模塊)
所有能被整合或?qū)氲狡渌黳ython腳本的代碼,都可以被稱為擴(kuò)展??梢杂肞ython來寫擴(kuò)展,也可以用C和C++之類的編譯型的語言來寫擴(kuò)展。Python在設(shè)計之初就考慮到要讓模塊的導(dǎo)入機(jī)制足夠抽象。抽象到讓使用模塊的代碼無法了解到模塊的具體實(shí)現(xiàn)細(xì)節(jié)。Python的可擴(kuò)展性具有的優(yōu)點(diǎn):方便為語言增加新功能、具有可定制性、代碼可以實(shí)現(xiàn)復(fù)用等。
為 Python 創(chuàng)建擴(kuò)展需要三個主要的步驟:創(chuàng)建應(yīng)用程序代碼、利用樣板來包裝代碼和編譯與測試。
(1)創(chuàng)建應(yīng)用程序代碼
#include <stdio.h> #include <stdlib.h> #include <string.h> 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; }
上述代碼中有兩個函數(shù),一個是遞歸求階乘的函數(shù)fac();另一個reverse()函數(shù)實(shí)現(xiàn)了一個簡單的字符串反轉(zhuǎn)算法,其主要目的是修改傳入的字符串,使其內(nèi)容完全反轉(zhuǎn),但不需要申請內(nèi)存后反著復(fù)制的方法。
(2)用樣板來包裝代碼
接口的代碼被稱為“樣板”代碼,它是 應(yīng)用程序代碼與Python解釋器之間進(jìn)行交互所必不可少的一部分。樣板主要分為4步:a、包含Python的頭文件;b、為每個模塊的每一個函數(shù)增加一個型如PyObject* Module_func()的包裝函數(shù);c、為每個模塊增加一個型如PyMethodDef ModuleMethods[]的數(shù)組;d、增加模塊初始化函數(shù)void initModule()。
#include <stdio.h> #include <stdlib.h> #include <string.h> int fac(int n) { if (n < 2) return(1); return (n)*fac(n-1); } char *reverse(char *s) { register char t, *p = s, *q = (s + (strlen(s) - 1)); while (s && (p < q)) { t = *p; *p++ = *q; *q-- = t; } return(s); } int test() { 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; } #include "Python.h" static PyObject * Extest_fac(PyObject *self, PyObject *args) { int num; if (!PyArg_ParseTuple(args, "i", &num)) return NULL; return (PyObject*)Py_BuildValue("i", fac(num)); } static PyObject * Extest_doppel(PyObject *self, PyObject *args) { char *orig_str; char *dupe_str; PyObject* retval; if (!PyArg_ParseTuple(args, "s", &orig_str)) return NULL; retval = (PyObject*)Py_BuildValue("ss", orig_str, dupe_str=reverse(strdup(orig_str))); free(dupe_str); #防止內(nèi)存泄漏 return retval; } static PyObject * Extest_test(PyObject *self, PyObject *args) { test(); return (PyObject*)Py_BuildValue(""); } static PyMethodDef ExtestMethods[] = { { "fac", Extest_fac, METH_VARARGS }, { "doppel", Extest_doppel, METH_VARARGS }, { "test", Extest_test, METH_VARARGS }, { NULL, NULL }, }; void initExtest() { Py_InitModule("Extest", ExtestMethods); }
Python.h頭文件在大多數(shù)類Unix系統(tǒng)中會在/usr/local/include/python2.x或/usr/include/python2.x目錄中,系統(tǒng)一般都會知道文件安裝的路徑。
增加包裝函數(shù),所在模塊名為Extest,那么創(chuàng)建一個包裝函數(shù)叫Extest_fac(),在Python腳本中使用是先import Extest,然后調(diào)用Extest.fac(),當(dāng) Extest.fac()被調(diào)用時,包裝函數(shù) Extest_fac()會被調(diào)用,包裝函數(shù)接受一個 Python的整數(shù)參數(shù),把它轉(zhuǎn)為C的整數(shù),然后調(diào)用C的fac()函數(shù),得到一個整型的返回值,最后把這個返回值轉(zhuǎn)為Python的整型數(shù)做為整個函數(shù)調(diào)用的結(jié)果返回回去。其他兩個包裝函數(shù)Extest_doppel()和Extest_test()類似。
從Python到C的轉(zhuǎn)換用PyArg_Parse*系列函數(shù), int PyArg_ParseTuple():把Python傳過來的參數(shù)轉(zhuǎn)為C;int PyArg_ParseTupleAndKeywords()與PyArg_ParseTuple()作用相同,但是同時解析關(guān)鍵字參數(shù);它們 的用法跟C的sscanf函數(shù)很像,都接受一個字符串流,并根據(jù)一個指定的格式字符串進(jìn)行解析,把結(jié)果放入到相應(yīng)的指針?biāo)傅淖兞恐腥ィ鼈兊姆祷刂禐?表示解析成功,返回值為0表示失敗。 從C到Python的轉(zhuǎn)換函數(shù)是PyObject* Py_BuildValue():把C的數(shù)據(jù)轉(zhuǎn)為Python的一個對象或一組對象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的參數(shù)按格式字符串所指定的格式轉(zhuǎn)換成一個Python的對象。
C與Python之間數(shù)據(jù)轉(zhuǎn)換的轉(zhuǎn)換代碼:
為每個模塊增加一個型如PyMethodDef ModuleMethods[]的數(shù)組,以便于Python解釋器能夠?qū)氩⒄{(diào)用它們,每一個數(shù)組都包含了函數(shù)在Python中的名字,相應(yīng)的包裝函數(shù)的名字以及一個METH_VARARGS常量,METH_VARARGS表示參數(shù)以tuple形式傳入。 若需要使用 PyArg_ParseTupleAndKeywords()函數(shù)來分析命名參數(shù)的話,還需要讓這個標(biāo)志常量與METH_KEYWORDS常量進(jìn)行邏輯與運(yùn)算常量 。數(shù)組最后用兩個NULL來表示函數(shù)信息列表的結(jié)束。
所有工作的最后一部分就是模塊的初始化函數(shù),調(diào)用Py_InitModule()函數(shù),并把模塊名和ModuleMethods[]數(shù)組的名字傳遞進(jìn)去,以便于解釋器能正確的調(diào)用模塊中的函數(shù)。
(3)編譯
為了讓新Python的擴(kuò)展能被創(chuàng)建,需要把它們與Python庫放在一起編譯,distutils包被用來編譯、安裝和分發(fā)這些模塊、擴(kuò)展和包。
創(chuàng)建一個setup.py 文件,編譯最主要的工作由setup()函數(shù)來完成:
#!/usr/bin/env python from distutils.core import setup, Extension MOD = 'Extest' setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])
Extension()第一個參數(shù)是(完整的)擴(kuò)展的名字,如果模塊是包的一部分的話,還要加上用'.'分隔的完整的包的名字。上述的擴(kuò)展是獨(dú)立的,所以名字只要寫"Extest"就行;sources參數(shù)是所有源代碼的文件列表,只有一個文件Extest2.c。setup需要兩個參數(shù):一個名字參數(shù)表示要編譯哪個內(nèi)容;另一個列表參數(shù)列出要編譯的對象,上述要編譯的是一個擴(kuò)展,故把ext_modules參數(shù)的值設(shè)為擴(kuò)展模塊的列表。
運(yùn)行setup.py build命令就可以開始編譯我們的擴(kuò)展了,提示部分信息:
creating build/lib.linux-x86_64-2.6 gcc -pthread -shared build/temp.linux-x86_64-2.6/Extest2.o -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/Extest.so
(4)導(dǎo)入和測試
你的擴(kuò)展會被創(chuàng)建在運(yùn)行setup.py腳本所在目錄下的build/lib.*目錄中,可以切換到那個目錄中來測試模塊,或者也可以用命令把它安裝到Python中:python setup.py install,會提示相應(yīng)信息。
測試模塊:
(5)引用計數(shù)和線程安全
Python對象引用計數(shù)的宏:Py_INCREF(obj)增加對象obj的引用計數(shù),Py_DECREF(obj)減少對象obj的引用計數(shù)。Py_INCREF()和Py_DECREF()兩個函數(shù)也有一個先檢查對象是否為空的版本,分別為Py_XINCREF()和Py_XDECREF()。
編譯擴(kuò)展的程序員必須要注意,代碼有可能會被運(yùn)行在一個多線程的Python環(huán)境中。這些線程使用了兩個C宏P(guān)y_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS, 通過將代碼和線程隔離,保證了運(yùn)行和非運(yùn)行時的安全性,由這些宏包裹的代碼將會允許其他線程的運(yùn)行。
三、C/C++調(diào)用Python
C++可以調(diào)用Python腳本,那么就可以寫一些Python的腳本接口供C++調(diào)用了,至少可以把Python當(dāng)成文本形式的動態(tài)鏈接庫,
需要的時候還可以改一改,只要不改變接口。缺點(diǎn)是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**/ #include <Python.h> int main(int argc, char** argv) { // 初始化Python //在使用Python系統(tǒng)前,必須使用Py_Initialize對其 //進(jìn)行初始化。它會載入Python的內(nèi)建模塊并添加系統(tǒng)路 //徑到模塊搜索路徑中。這個函數(shù)沒有返回值,檢查系統(tǒng) //是否初始化成功需要使用Py_IsInitialized。 Py_Initialize(); // 檢查初始化是否成功 if ( !Py_IsInitialized() ) { return -1; } // 添加當(dāng)前路徑 //把輸入的字符串作為Python代碼直接運(yùn)行,返回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; } // 找出函數(shù)名為add的函數(shù) printf("----------------------\n"); pFunc = PyDict_GetItemString(pDict, "add"); if ( !pFunc || !PyCallable_Check(pFunc) ) { printf("can't find function [add]"); getchar(); return -1; } // 參數(shù)進(jìn)棧 PyObject *pArgs; pArgs = PyTuple_New(2); // PyObject* Py_BuildValue(char *format, ...) // 把C++的變量轉(zhuǎn)換成一個Python對象。當(dāng)需要從 // C++傳遞變量到Python時,就會使用這個函數(shù)。此函數(shù) // 有點(diǎn)類似C的printf,但格式不同。常用的格式有 // s 表示字符串, // i 表示整型變量, // f 表示浮點(diǎn)數(shù), // O 表示一個Python對象。 PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3)); PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4)); // 調(diào)用Python函數(shù) PyObject_CallObject(pFunc, pArgs); //下面這段是查找函數(shù)foo 并執(zhí)行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); // 關(guān)閉Python Py_Finalize(); return 0; }
(3)C++編譯成二進(jìn)制可執(zhí)行文件:g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6,編譯選項需要手動指定Python的include路徑和鏈接接路徑(Python版本號根據(jù)具體情況而定)。
(4)運(yùn)行結(jié)果:
四、總結(jié)
(1)Python和C/C++的相互調(diào)用僅是測試代碼,具體的項目開發(fā)還得參考Python的API文檔。
(2)兩者交互,C++可為Python編寫擴(kuò)展模塊,Python也可為C++提供腳本接口,更加方便于實(shí)際應(yīng)用。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Python實(shí)現(xiàn)爬蟲從網(wǎng)絡(luò)上下載文檔的實(shí)例代碼
小編最近在研究python,接觸到了爬蟲,本文給大家?guī)砹薖ython實(shí)現(xiàn)爬蟲從網(wǎng)絡(luò)上下載文檔的知識。下面小編把具體實(shí)例代碼分享到腳本之家平臺,感興趣的朋友參考下吧2018-06-06python使用socket創(chuàng)建tcp服務(wù)器和客戶端
這篇文章主要為大家詳細(xì)介紹了python使用socket創(chuàng)建tcp服務(wù)器和客戶端,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-04-04Python在for循環(huán)里處理大數(shù)據(jù)的推薦方法實(shí)例
這篇文章主要介紹了Python在for循環(huán)里處理大數(shù)據(jù)的推薦方法實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01python BitMap算法處理20億隨機(jī)整數(shù)去重
這篇文章主要為大家介紹了python BitMap算法處理20億隨機(jī)整數(shù)去重,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01