[轉] C++ 和 python之間的互相呼叫
轉載自:https://www.cnblogs.com/apexchu/p/5015961.html
一、Python呼叫C/C++
1、Python呼叫C動態連結庫
Python呼叫C庫比較簡單,不經過任何封裝打包成so,再使用python的ctypes呼叫即可。
(1)C語言檔案:pycall.c
1 /***gcc -o libpycall.so -shared -fPIC pycall.c*/ 2 #include <stdio.h> 3 #include <stdlib.h> 4int foo(int a, int b) 5 { 6 printf("you input %d and %d\n", a, b); 7 return a+b; 8 }
(2)gcc編譯生成動態庫libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++編譯生成C動態庫的程式碼中的函式或者方法時,需要使用extern "C"來進行編譯。
(3)Python呼叫動態庫的檔案:pycall.py
1 import ctypes 2 ll = ctypes.cdll.LoadLibrary3 lib = ll("./libpycall.so") 4 lib.foo(1, 3) 5 print '***finish***'
(4)執行結果:
2、Python呼叫C++(類)動態連結庫
需要extern "C"來輔助,也就是說還是隻能呼叫C函式,不能直接呼叫方法,但是能解析C++方法。不是用extern "C",構建後的動態連結庫沒有這些函式的符號表。
(1)C++類檔案:pycallclass.cpp
1 #include <iostream> 2 using namespace std; 3 4 classTestLib 5 { 6 public: 7 void display(); 8 void display(int a); 9 }; 10 void TestLib::display() { 11 cout<<"First display"<<endl; 12 } 13 14 void TestLib::display(int a) { 15 cout<<"Second display:"<<a<<endl; 16 } 17 extern "C" { 18 TestLib obj; 19 void display() { 20 obj.display(); 21 } 22 void display_int() { 23 obj.display(2); 24 } 25 }
(3)Python呼叫動態庫的檔案:pycallclass.py
1 import ctypes 2 so = ctypes.cdll.LoadLibrary 3 lib = so("./libpycallclass.so") 4 print 'display()' 5 lib.display() 6 print 'display(100)' 7 lib.display_int(100)
(4)執行結果
3、Python呼叫C/C++可執行程式
(1)C/C++程式:main.cpp
1 #include <iostream> 2 using namespace std; 3 int test() 4 { 5 int a = 10, b = 5; 6 return a+b; 7 } 8 int main() 9 { 10 cout<<"---begin---"<<endl; 11 int num = test(); 12 cout<<"num="<<num<<endl; 13 cout<<"---end---"<<endl; 14 }
(3)Python呼叫程式:main.py
1 import commands 2 import os 3 main = "./testmain" 4 if os.path.exists(main): 5 rc, out = commands.getstatusoutput(main) 6 print 'rc = %d, \nout = %s' % (rc, out) 7 8 print '*'*10 9 f = os.popen(main) 10 data = f.readlines() 11 f.close() 12 print data 13 14 print '*'*10 15 os.system(main)
(4)執行結果:
4、擴充套件Python(C++為Python編寫擴充套件模組)
所有能被整合或匯入到其它python指令碼的程式碼,都可以被稱為擴充套件。可以用Python來寫擴充套件,也可以用C和C++之類的編譯型的語言來寫擴充套件。Python在設計之初就考慮到要讓模組的匯入機制足夠抽象。抽象到讓使用模組的程式碼無法瞭解到模組的具體實現細節。Python的可擴充套件性具有的優點:方便為語言增加新功能、具有可定製性、程式碼可以實現複用等。
為 Python 建立擴充套件需要三個主要的步驟:建立應用程式程式碼、利用樣板來包裝程式碼和編譯與測試。
(1)建立應用程式程式碼
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int fac(int n) 6 { 7 if (n < 2) return(1); /* 0! == 1! == 1 */ 8 return (n)*fac(n-1); /* n! == n*(n-1)! */ 9 } 10 11 char *reverse(char *s) 12 { 13 register char t, /* tmp */ 14 *p = s, /* fwd */ 15 *q = (s + (strlen(s) - 1)); /* bwd */ 16 17 while (p < q) /* if p < q */ 18 { 19 t = *p; /* swap & move ptrs */ 20 *p++ = *q; 21 *q-- = t; 22 } 23 return(s); 24 } 25 26 int main() 27 { 28 char s[BUFSIZ]; 29 printf("4! == %d\n", fac(4)); 30 printf("8! == %d\n", fac(8)); 31 printf("12! == %d\n", fac(12)); 32 strcpy(s, "abcdef"); 33 printf("reversing 'abcdef', we get '%s'\n", \ 34 reverse(s)); 35 strcpy(s, "madam"); 36 printf("reversing 'madam', we get '%s'\n", \ 37 reverse(s)); 38 return 0; 39 }
上述程式碼中有兩個函式,一個是遞迴求階乘的函式fac();另一個reverse()函式實現了一個簡單的字串反轉演算法,其主要目的是修改傳入的字串,使其內容完全反轉,但不需要申請記憶體後反著複製的方法。 (2)用樣板來包裝程式碼
介面的程式碼被稱為“樣板”程式碼,它是應用程式程式碼與Python直譯器之間進行互動所必不可少的一部分。樣板主要分為4步:a、包含Python的標頭檔案;b、為每個模組的每一個函式增加一個型如PyObject* Module_func()的包裝函式;c、為每個模組增加一個型如PyMethodDef ModuleMethods[]的陣列;d、增加模組初始化函式void initModule()。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int fac(int n) 6 { 7 if (n < 2) return(1); 8 return (n)*fac(n-1); 9 } 10 11 char *reverse(char *s) 12 { 13 register char t, 14 *p = s, 15 *q = (s + (strlen(s) - 1)); 16 17 while (s && (p < q)) 18 { 19 t = *p; 20 *p++ = *q; 21 *q-- = t; 22 } 23 return(s); 24 } 25 26 int test() 27 { 28 char s[BUFSIZ]; 29 printf("4! == %d\n", fac(4)); 30 printf("8! == %d\n", fac(8)); 31 printf("12! == %d\n", fac(12)); 32 strcpy(s, "abcdef"); 33 printf("reversing 'abcdef', we get '%s'\n", \ 34 reverse(s)); 35 strcpy(s, "madam"); 36 printf("reversing 'madam', we get '%s'\n", \ 37 reverse(s)); 38 return 0; 39 } 40 41 #include "Python.h" 42 43 static PyObject * 44 Extest_fac(PyObject *self, PyObject *args) 45 { 46 int num; 47 if (!PyArg_ParseTuple(args, "i", &num)) 48 return NULL; 49 return (PyObject*)Py_BuildValue("i", fac(num)); 50 } 51 52 static PyObject * 53 Extest_doppel(PyObject *self, PyObject *args) 54 { 55 char *orig_str; 56 char *dupe_str; 57 PyObject* retval; 58 59 if (!PyArg_ParseTuple(args, "s", &orig_str)) 60 return NULL; 61 retval = (PyObject*)Py_BuildValue("ss", orig_str, 62 dupe_str=reverse(strdup(orig_str))); 63 free(dupe_str); #防止記憶體洩漏 64 return retval; 65 } 66 67 static PyObject * 68 Extest_test(PyObject *self, PyObject *args) 69 { 70 test(); 71 return (PyObject*)Py_BuildValue(""); 72 } 73 74 static PyMethodDef 75 ExtestMethods[] = 76 { 77 { "fac", Extest_fac, METH_VARARGS }, 78 { "doppel", Extest_doppel, METH_VARARGS }, 79 { "test", Extest_test, METH_VARARGS }, 80 { NULL, NULL }, 81 }; 82 83 void initExtest() 84 { 85 Py_InitModule("Extest", ExtestMethods); 86 }
增加包裝函式,所在模組名為Extest,那麼建立一個包裝函式叫Extest_fac(),在Python指令碼中使用是先import Extest,然後呼叫Extest.fac(),當Extest.fac()被呼叫時,包裝函式Extest_fac()會被呼叫,包裝函式接受一個 Python的整數引數,把它轉為C的整數,然後呼叫C的fac()函式,得到一個整型的返回值,最後把這個返回值轉為Python的整型數做為整個函式呼叫的結果返回回去。其他兩個包裝函式Extest_doppel()和Extest_test()類似。
從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()函式來完成:
1 #!/usr/bin/env python 2 3 from distutils.core import setup, Extension 4 5 MOD = 'Extest' 6 setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])
執行setup.py build命令就可以開始編譯我們的擴充套件了,提示部分資訊:
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
你的擴充套件會被建立在執行setup.py指令碼所在目錄下的build/lib.*目錄中,可以切換到那個目錄中來測試模組,或者也可以用命令把它安裝到Python中:python setup.py install,會提示相應資訊。
測試模組:
(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
1 #test function 2 def add(a,b): 3 print "in python function add" 4 print "a = " + str(a) 5 print "b = " + str(b) 6 print "ret = " + str(a+b) 7 return 8 9 def foo(a): 10 11 print "in python function foo" 12 print "a = " + str(a) 13 print "ret = " + str(a * a) 14 return 15 16 class guestlist: 17 def __init__(self): 18 print "aaaa" 19 def p(): 20 print "bbbbb" 21 def __getitem__(self, id): 22 return "ccccc" 23 def update(): 24 guest = guestlist() 25 print guest['aa'] 26 27 #update()
(2)C++程式碼:
1 /**g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6**/ 2 #include <Python.h> 3 int main(int argc, char** argv) 4 { 5 // 初始化Python 6 //在使用Python系統前,必須使用Py_Initialize對其 7 //進行初始化。它會載入Python的內建模組並新增系統路 8 //徑到模組搜尋路徑中。這個函式沒有返回值,檢查系統 9 //是否初始化成功需要使用Py_IsInitialized。 10 Py_Initialize(); 11 12 // 檢查初始化是否成功 13 if ( !Py_IsInitialized() ) { 14 return -1; 15 } 16 // 添加當前路徑 17 //把輸入的字串作為Python程式碼直接執行,返回0 18 //表示成功,-1表示有錯。大多時候錯誤都是因為字串 19 //中有語法錯誤。 20 PyRun_SimpleString("import sys"); 21 PyRun_SimpleString("print '---import sys---'"); 22 PyRun_SimpleString("sys.path.append('./')"); 23 PyObject *pName,*pModule,*pDict,*pFunc,*pArgs; 24 25 // 載入名為pytest的指令碼 26 pName = PyString_FromString("pytest"); 27 pModule = PyImport_Import(pName); 28 if ( !pModule ) { 29 printf("can't find pytest.py"); 30 getchar(); 31 return -1; 32 } 33 pDict = PyModule_GetDict(pModule); 34 if ( !pDict ) { 35 return -1; 36 } 37 38 // 找出函式名為add的函式 39 printf("----------------------\n"); 40 pFunc = PyDict_GetItemString(pDict, "add"); 41 if ( !pFunc || !PyCallable_Check(pFunc) ) { 42 printf("can't find function [add]"); 43 getchar(); 44 return -1; 45 } 46 47 // 引數進棧 48 *pArgs; 49 pArgs = PyTuple_New(2); 50 51 // PyObject* Py_BuildValue(char *format, ...) 52 // 把C++的變數轉換成一個Python物件。當需要從 53 // C++傳遞變數到Python時,就會使用這個函式。此函式 54 // 有點類似C的printf,但格式不同。常用的格式有 55 // s 表示字串, 56 // i 表示整型變數, 57 // f 表示浮點數, 58 // O 表示一個Python物件。 59 60 PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",3)); 61 PyTuple_SetItem(pArgs, 1, Py_BuildValue("l",4)); 62 63 // 呼叫Python函式 64 PyObject_CallObject(pFunc, pArgs); 65 66 //下面這段是查詢函式foo 並執行foo 67 printf("----------------------\n"); 68 pFunc = PyDict_GetItemString(pDict, "foo"); 69 if ( !pFunc || !PyCallable_Check(pFunc) ) { 70 printf("can't find function [foo]"); 71 getchar(); 72 return -1; 73 } 74 75 pArgs = PyTuple_New(1); 76 PyTuple_SetItem(pArgs, 0, Py_BuildValue("l",2)); 77 78 PyObject_CallObject(pFunc, pArgs); 79 80 printf("----------------------\n"); 81 pFunc = PyDict_GetItemString(pDict, "update"); 82 if ( !pFunc || !PyCallable_Check(pFunc) ) { 83 printf("can't find function [update]"); 84 getchar(); 85 return -1; 86 } 87 pArgs = PyTuple_New(0); 88 PyTuple_SetItem(pArgs, 0, Py_BuildValue("")); 89 PyObject_CallObject(pFunc, pArgs); 90 91 Py_DECREF(pName); 92 Py_DECREF(pArgs); 93 Py_DECREF(pModule); 94 95 // 關閉Python 96 Py_Finalize(); 97 return 0; 98 }
(3)C++編譯成二進位制可執行檔案:g++ -o callpy callpy.cpp -I/usr/include/python2.6 -L/usr/lib64/python2.6/config -lpython2.6,編譯選項需要手動指定Python的include路徑和連結接路徑(Python版本號根據具體情況而定)。
(4)執行結果: