1. 程式人生 > >python與C/C++相互呼叫

python與C/C++相互呼叫

C/C++呼叫python

python作為一種膠水語言可以很靈活的嵌入到C++和java等主語言裡面進行互操作實現擴充套件功能。

方法1:使用python提供的C介面(基礎)

使用python提供給C/C++的API,將python程式程式設計文字形式的動態連結庫,可以熱更新,非常方便。

API介紹

以下是一些API的介紹:

void Py_Initialize(void)

初始化Python直譯器,如果初始化失敗,繼續下面的呼叫會出現各種錯誤,可惜的是此函式沒有返回值來判斷是否初始化成功,如果失敗會導致致命錯誤。

int Py_IsInitialized(void)

檢查是否已經進行了初始化,如果返回0,表示沒有進行過初始化。

void Py_Finalize()

反初始化Python直譯器,包括子直譯器,呼叫此函式同時會釋放Python直譯器所佔用的資源。

int PyRun_SimpleString(const char *command)

實際上是一個巨集,執行一段Python程式碼。

PyObject* PyImport_ImportModule(char *name)

匯入一個Python模組,引數name可以是*.py檔案的檔名。類似Python內建函式import。

PyObject* PyModule_GetDict( PyObject *module)

相當於Python模組物件的dict屬性,得到模組名稱空間下的字典物件。

PyObject* PyRun_String(const char* str, int start,PyObject* globals, PyObject* locals)

執行一段Python程式碼。

int PyArg_Parse(PyObject* args, char* format, …)

把Python資料型別解析為C的型別,這樣C程式中才可以使用Python裡面的資料。

PyObject* PyObject_GetAttrString(PyObject o, charattr_name)

返回模組物件o中的attr_name 屬性或函式,相當於Python中表達式語句,o.attr_name。

PyObject* Py_BuildValue(char* format, …)

和PyArg_Parse剛好相反,構建一個引數列表,把C型別轉換為Python物件,使得Python裡面可以使用C型別資料。

PyObject* PyEval_CallObject(PyObject* pfunc, PyObject*pargs)

此函式有兩個引數,而且都是Python物件指標,其中pfunc是要呼叫的Python 函式,一般說來可以使用PyObject_GetAttrString()獲得,pargs是函式的引數列表,通常是使用Py_BuildValue()來構建。

C++向Python傳遞引數

C++向Python傳引數是以元組(tuple)的方式傳過去的,因此我們實際上就是構造一個合適的Python元組就可以了,要用到PyTuple_New,Py_BuildValue,PyTuple_SetItem等幾個函式,其中Py_BuildValue可以有其它一些的替換函式。

PyObject* pyParams = PyTuple_New(2);

   PyObject* pyParams1= Py_BuildValue("i",5);

   PyObject* pyParams2= Py_BuildValue("i",6);

   PyTuple_SetItem(pyParams,0, pyParams1);

   PyTuple_SetItem(pyParams,1, pyParams2);

   pRet = PyEval_CallObject(pFunc, pyParams);

也可以直接使用PyObject* Py_BuildValue(char *format, …) 函式來直接來構造tuple,此函式的使用也很簡單,記住一些轉換的格式常量即可輕鬆進行轉換(格式常量有點類似printf)。譬如s 表示字串,i表示整型變數,f表示浮點數,o表示一個Pytho物件等等

Py_BuildValue("")                       None

Py_BuildValue("i",123)                 123

Py_BuildValue("iii",123, 456, 789)     (123, 456, 789)

Py_BuildValue("s","hello")             'hello'

Py_BuildValue("ss","hello", "world")    ('hello', 'world')

Py_BuildValue("s#","hello", 4)         'hell'

Py_BuildValue("()")                     ()

Py_BuildValue("(i)",123)               (123,)

Py_BuildValue("(ii)",123, 456)         (123, 456)

Py_BuildValue("(i,i)",123, 456)        (123, 456)

Py_BuildValue("[i,i]",123, 456)        [123, 456]

Py_BuildValue("{s:i,s:i}",

             "abc", 123, "def", 456)    {'abc': 123, 'def': 456}

Py_BuildValue("((ii)(ii))(ii)",

             1, 2, 3, 4, 5, 6)         (((1, 2), (3, 4)), (5, 6))

C++獲得Python的返回值

Python傳回給C++的都是PyObject物件,因此可以呼叫Python裡面的一些型別轉換API來把返回值轉換成C++裡面的型別。類似PyInt_AsLong,PyFloat_AsDouble這些系列的函式。Python比較喜歡傳回一個元組,可以使用PyArg_ParseTuple這個函式來解析。這個函式也要用到上面的格式常量)。還有一個比較通用的轉換函式是PyArg_Parse,也需要用到格式常量。

原始碼測試

建一個python檔案
python_called.py

def add_func(x,y):
    return x+y

在同目錄下建C檔案或者C++檔案
main.cpp

#include <iostream>
#include "Python.h" //這裡要包含標頭檔案

//C/C++中呼叫python函式的函式,這裡採用單返回值
int function_from_python(int a,int b)
{
    //初始化
    Py_Initialize();

    //定義引數
    int res;
    PyObject *pModule=NULL;
    PyObject *pFunc=NULL;
    PyObject *pArgs=NULL;
    PyObject *pResult=NULL;

    //匯入被呼叫的py檔名
   if(!(pModule=PyImport_Import(PyString_FromString("python_called"))))
    {
        std::cout<<"get module failed!"<<std::endl;
        exit(0);
      }

    //獲得要呼叫的函式名
    if(!(pFunc=PyObject_GetAttrString(pModule, "add_func")))
    {
        std::cout<<"get func failed!"<<std::endl;
        exit(0);
    }

    //傳入引數
    pArgs=Py_BuildValue("ii",a,b);

    //執行函式
    pResult=PyObject_CallObject(pFunc, pArgs);

    //返回值為C++
    res = PyInt_AsLong(pResult);

    //釋放
    if(pArgs)
        Py_DECREF(pArgs);
    if(pFunc)
        Py_DECREF(pFunc);

    Py_Finalize();

    return res;
}

int main()
{
    std::cout<<"C/C++ call python function:"<<std::endl;
    std::cout<<function_from_python(3,5)<<std::endl;
    return 0;
}

在Windows平臺下,開啟Visual Studio命令提示符,編譯命令為

cl main.cpp -I C:\Python27\include C:\Python27\libs\python27.lib

在Linux下編譯命令為

g++ main.cpp -o main -I/usr/include/python2.7/ -lpython2.7

在Mac OS X 下的編譯命令同上

編譯完後執行可執行檔案

C/C++ call python function:
8

注意:

  • 被呼叫的python檔案必須與C++編譯出來的可執行檔案放在一個目錄。
  • 可以建vs2013工程或者Qt工程或則makefile工程檔案,在裡面配置include和lib目錄,更方便。

方法2:呼叫python檔案並執行(基礎)

還可以使用C/C++直接執行python檔案程式,在控制檯中執行。

原始碼測試

建一個python檔案
python_called.py

def add_func(x,y):
    return x+y

a=13
b=10
print "the result by python func:"
print add_func(a,b)

建C/C++檔案
main.cpp

include <iostream>

include "Python.h" //這裡要包含標頭檔案

//C/C++中執行python檔案
void exec_python_file()
{
    //初始化
    Py_Initialize();

    //choose1,執行單純的內嵌字串python程式碼,建議使用
    if(!PyRun_SimpleString("execfile('python_called.py')"))
        std::cout<<"execute python file program failed"<<std::endl;

    //choose2,執行python檔案,不建議使用
    // char fileStr[]="python_called.py";
    // FILE *fp;
    // if(!(fp=fopen(fileStr,"r")))
    // std::cout<<"open python file failed!"<<std::endl;

    // if(!PyRun_SimpleFile(fp,fileStr))
    // std::cout<<"execute python file failed!"<<std::endl;

    // fclose(fp);

        //釋放資源
        Py_Finalize();
}

int main()
{
    std::cout<<"C/C++ call python function:"<<std::endl;
    exec_python_file();
    return 0;
}

同樣採用命令列或者IDE配置依賴項後編譯執行。
執行結果

C/C++ call python function: the result by python func: 23

注意:

  • 同樣的py檔案必須和C/C++可執行檔案在同一個目錄。
  • PyRun_SimpleString方式其實是讀一段字串程式,可以用FILE或者fstream讀進來文字檔案然後傳入也行,這樣就可以用相對目錄了。
  • 不建議用PyRun_SimpleFile的方式,因為這個API要求傳入一個FILE指標,而微軟的幾個CRT版本FILE指標的定義有了變化,因此傳入你使用VS2005編譯的FILE指標或者其它版本的FILE極有可能崩潰,如果你想安全呼叫,最好是自己把Python的原始碼使用和應用程式相同的環境一起編譯出lib來使用。

方法3:使用Cpython(高階)

這是python的一個第三方元件,把Python程式碼直接變成C程式碼,此處略。

python呼叫C/C++

方法1:呼叫C/C++動態連結庫(基礎)

將C/C++的程式不經任何加工直接編譯成動態連結庫so或者dll,再使用python的ctypes呼叫即可

原始碼測試

此處僅以linux下的so為例,因為windows下VS2013生成dll還有個匯出庫很麻煩。
cpp_dll.cpp

#include <iostream>
extern "C"
void add_func(int a,int b)
{
    std::cout<<"the result: "<<a+b<<std::endl;
}

在linux或者mac下用命令列編譯成so

g++ -o cpp_dll.so -shared -fPIC cpp_dll.cpp

也可以在makefile裡面配置

在windows下用vs2013的命令列編譯從dll

cl /LD cpp_dll.cpp -I C:\Python27\include C:\Python27\libs\python27.lib

在windows下也可以用IDE生成dll
main.py

import ctypes
dll=ctypes.cdll.LoadLibrary
lib=dll("./cpp_dll.so") #in windows use dll
print "python call cpp dll:"
lib.add_func(2,3)

執行main.py即可

注意:

  • C++程式碼需要加extern “C”來按照C語言編譯連結
  • 裝在動態庫的路徑可以用相對路徑

方法2:呼叫C/C++編寫的python擴充套件模組(基礎)

這種方法比較好,用C/C++編寫python的擴充套件模組,在python程式裡面import進去就可以呼叫介面

原始碼測試

cpp_called.cpp

#include "Python.h"

extern "C"
int add_func(int a,int b) 
{
    return a+b;
}

extern "C"
static PyObject *_add_func(PyObject *self, PyObject *args)
{
    int _a,_b;
    int res;

    if (!PyArg_ParseTuple(args, "ii", &_a, &_b))
        return NULL;
    res = add_func(_a, _b);
    return PyLong_FromLong(res);
}

extern "C"
static PyMethodDef CppModuleMethods[] = 
{
    {
        "add_func",
        _add_func,
        METH_VARARGS,
        ""
    },
    {NULL, NULL, 0, NULL}
};

extern "C"
PyMODINIT_FUNC initcpp_module(void) 
{
    (void) Py_InitModule("cpp_module", CppModuleMethods);

函式介紹:

  • 包裹函式_add_func。它負責將Python的引數轉化為C的引數(PyArg_ParseTuple),呼叫實際的add_function,並處理add_function的返回值,最終返回給Python環境。
  • 引數解析PyArg_ParseTuple,將python的變數解析成C/C++變數,按照ii,si,ss等格式
  • 匯出表CppModuleMethods。它負責告訴Python這個模組裡有哪些函式可以被Python呼叫。匯出表的名字可以隨便起,每一項有4個引數:第一個引數是提供給Python環境的函式名稱,第二個引數是_add_function,即包裹函式。第三個引數的含義是引數變長,第四個引數是一個說明性的字串。匯出表總是以{NULL,NULL, 0,NULL}結束。
  • 匯出函式initcpp_module。這個的名字不是任取的,是你的module名稱新增字首init。匯出函式中將模組名稱與匯出表進行連線。

在windows下,用vs2013命令列編譯成pyd檔案,這個檔案就可以被python識別成擴充套件模組

cl /LD cpp_called.cpp /o cpp_module.pyd -I C:\Python27\include C:\Python27\libs\python27.lib

也可以在IDE裡面配置編譯選項生成。

在linux或者mac系統下命令編譯

g++ -fPIC -shared cpp_called.cpp -o cpp_module.so -I /usr/include/python2.7/ -lpython2.7

main.py

from cpp_module import add_func
print "python call C/C++ function:"
print add_func(7,12)

執行main.py檔案

python call C/C++ function:
19

注意:

  • 按照C語言編譯連結
  • 編譯的模組放在python檔案能識別的目錄,最好放在同一個目錄

方法3:呼叫二進位制可執行檔案(基礎)

用python程式呼叫C/C++編譯的可執行檔案
cppexec.cpp

#include <iostream>

int add_func(int a,int b)
{
    return a+b;
}

int main()
{
    std::cout<<"the C/C++ run result:"<<std::endl;
    std::cout<<add_func(2,3)<<std::endl;
    return 0;
}

用命令列或者IDE編譯成exe等執行檔案
main.py

import os

cpptest="cppexec.exe" #in linux without suffix .exe
if os.path.exists(cpptest):
    f=os.popen(cpptest)
    data=f.readlines() #read the C++ printf or cout content
    f.close()
    print data

print "python execute cpp program:"
os.system(cpptest)

注意:

  • 可執行檔案放在python檔案可識別的目錄,最好同一目錄

方法4:使用 SWIG(高階)

這是一個第三方的針對python的擴充套件包,需要些配置檔案,略。

python呼叫C/C++
一般性地給出三個推薦:

  • ctypes,在Python呼叫已經編譯打包好的C語言動態連結庫。
  • SWIG,通過宣告一個.i檔案(語法類似.h),用額外安裝的swig命令自動生成一個C/C++與一個Python的包裝檔案,省略了手寫這兩層包裝的工作。
  • Boost.Python,這是Boost專案的一部分,本質上是對#include <Python.h>的一個包裝。開發略顯複雜,難以配置編譯。

簡單起見,推薦SWIG。它還有distutils/setuptools的原生支援,配置起來非常方便。

喜歡的朋友可以加QQ群813622576,群內有免費資料供大家一起交流學習哦!!