1. 程式人生 > >[轉] C++ 和 python之間的互相呼叫

[轉] 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>  
4
int 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.LoadLibrary   
3 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 class
TestLib 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)執行結果: