Python執行個體淺談之三Python與C/C++相互調用

來源:互聯網
上載者:User

標籤:python

一、問題

      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 <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編譯產生動態庫libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++編譯產生C動態庫的代碼中的函數或者方法時,需要使用extern "C"來進行編譯。
(3)Python調用動態庫的檔案:pycall.py
import ctypesll = 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
#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++編譯產生動態庫libpycall.so:g++ -o libpycallclass.so -shared -fPIC pycallclass.cpp。
(3)Python調用動態庫的檔案:pycallclass.py
import ctypesso = ctypes.cdll.LoadLibrary lib = so("./libpycallclass.so") print 'display()'lib.display()print 'display(100)'lib.display_int(100)
(4)運行結果:

3、Python調用C/C++可執行程式(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)編譯成二進位可執行檔:g++ -o testmain main.cpp。
(3)Python調用程式:main.py
import commandsimport osmain = "./testmain"if os.path.exists(main):    rc, out = commands.getstatusoutput(main)    print 'rc = %d, \nout = %s' % (rc, out)print '*'*10f = os.popen(main)  data = f.readlines()  f.close()  print dataprint '*'*10os.system(main)
(4)運行結果:

4、擴充Python(C++為Python編寫擴充模組)       所有能被整合或匯入到其它python指令碼的代碼,都可以被稱為擴充。可以用Python來寫擴充,也可以用C和C++之類的編譯型的語言來寫擴充。Python在設計之初就考慮到要讓模組的匯入機制足夠抽象。抽象到讓使用模組的代碼無法瞭解到模組的具體實現細節。Python的可擴充性具有的優點:方便為語言增加新功能、具有可定製性、代碼可以實現複用等。
       為 Python 建立擴充需要三個主要的步驟:建立應用程式代碼、利用樣板來封裝代碼和編譯與測試。
(1)建立應用程式代碼
#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;}
       上述代碼中有兩個函數,一個是遞迴求階乘的函數fac();另一個reverse()函數實現了一個簡單的字串反轉演算法,其主要目的是修改傳入的字串,使其內容完全反轉,但不需要申請記憶體後反著複製的方法。
(2)用樣板來封裝代碼
        介面的代碼被稱為“樣板”代碼,它是應用程式代碼與Python解譯器之間進行互動所必不可少的一部分。樣板主要分為4步:a、包含Python的標頭檔;b、為每個模組的每一個函數增加一個型如PyObject* Module_func()的封裝函數;c、為每個模組增加一個型如PyMethodDef ModuleMethods[]的數組;d、增加模組初始化函數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);             #防止記憶體流失    return retval;}static PyObject *Extest_test(PyObject *self, PyObject *args){    test();    return (PyObject*)Py_BuildValue("");}static PyMethodDefExtestMethods[] ={    { "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標頭檔在大多數類Unix系統中會在/usr/local/include/python2.x或/usr/include/python2.x目錄中,系統一般都會知道檔案安裝的路徑。
        增加封裝函數,所在模組名為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()函數來完成:
#!/usr/bin/env pythonfrom distutils.core import setup, ExtensionMOD = 'Extest'setup(name=MOD, ext_modules=[Extension(MOD, sources=['Extest2.c'])])
        Extension()第一個參數是(完整的)擴充的名字,如果模組是包的一部分的話,還要加上用‘.‘分隔的完整的包的名字。上述的擴充是獨立的,所以名字只要寫"Extest"就行;sources參數是所有原始碼的檔案清單,只有一個檔案Extest2.c。setup需要兩個參數:一個名字參數表示要編譯哪個內容;另一個列表參數列出要編譯的對象,上述要編譯的是一個擴充,故把ext_modules參數的值設為擴充模組的列表。
        運行setup.py build命令就可以開始編譯我們的擴充了,提示部分資訊:
creating build/lib.linux-x86_64-2.6gcc -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)匯入和測試
         你的擴充會被建立在運行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

#test functiondef add(a,b):    print "in python function add"    print "a = " + str(a)    print "b = " + str(b)    print "ret = " + str(a+b)    returndef 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系統前,必須使用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)運行結果:

四、總結(1)Python和C/C++的相互調用僅是測試代碼,具體的項目開發還得參考Python的API文檔。
(2)兩者互動,C++可為Python編寫擴充模組,Python也可為C++提供指令碼介面,更加方便於實際應用。
(3)若有不足,請留言,在此先感謝!

Python執行個體淺談之三Python與C/C++相互調用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.