python與C/C++相互調(diào)用

https://www.zhihu.com/question/23003213 知乎詳細總結(jié)
http://www.voidcn.com/article/p-wmqbbket-bdm.html 實例
http://www.voidcn.com/article/p-zgwjtool-bdh.html 實例

C/C++調(diào)用python

python作為一種膠水語言可以很靈活的嵌入到C++和java等主語言里面進行互操作實現(xiàn)擴展功能。

方法1:使用python提供的C接口(基礎(chǔ))

使用python提供給C/C++的API,將python程序編程文本形式的動態(tài)鏈接庫,可以熱更新,非常方便。

API介紹

以下是一些API的介紹:

void Py_Initialize(void)

初始化Python解釋器,如果初始化失敗,繼續(xù)下面的調(diào)用會出現(xiàn)各種錯誤,可惜的是此函數(shù)沒有返回值來判斷是否初始化成功,如果失敗會導致致命錯誤。

int Py_IsInitialized(void)

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

void Py_Finalize()

反初始化Python解釋器,包括子解釋器,調(diào)用此函數(shù)同時會釋放Python解釋器所占用的資源。

int PyRun_SimpleString(const char *command)

實際上是一個宏,執(zhí)行一段Python代碼。

PyObject* PyImport_ImportModule(char *name)

導入一個Python模塊,參數(shù)name可以是*.py文件的文件名。類似Python內(nèi)建函數(shù)import。

PyObject* PyModule_GetDict( PyObject *module)

相當于Python模塊對象的dict屬性,得到模塊名稱空間下的字典對象。

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

執(zhí)行一段Python代碼。

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

把Python數(shù)據(jù)類型解析為C的類型,這樣C程序中才可以使用Python里面的數(shù)據(jù)。

PyObject* PyObject_GetAttrString(PyObject o, charattr_name)

返回模塊對象o中的attr_name 屬性或函數(shù),相當于Python中表達式語句,o.attr_name。

PyObject* Py_BuildValue(char* format, …)

和PyArg_Parse剛好相反,構(gòu)建一個參數(shù)列表,把C類型轉(zhuǎn)換為Python對象,使得Python里面可以使用C類型數(shù)據(jù)。

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

此函數(shù)有兩個參數(shù),而且都是Python對象指針,其中pfunc是要調(diào)用的Python 函數(shù),一般說來可以使用PyObject_GetAttrString()獲得,pargs是函數(shù)的參數(shù)列表,通常是使用Py_BuildValue()來構(gòu)建。

C++向Python傳遞參數(shù)

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

   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, …) 函數(shù)來直接來構(gòu)造tuple,此函數(shù)的使用也很簡單,記住一些轉(zhuǎn)換的格式常量即可輕松進行轉(zhuǎn)換(格式常量有點類似printf)。譬如s 表示字符串,i表示整型變量,f表示浮點數(shù),o表示一個Python對象等等。

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對象,因此可以調(diào)用Python里面的一些類型轉(zhuǎn)換API來把返回值轉(zhuǎn)換成C++里面的類型。類似PyInt_AsLong,PyFloat_AsDouble這些系列的函數(shù)。Python比較喜歡傳回一個元組,可以使用PyArg_ParseTuple這個函數(shù)來解析。這個函數(shù)也要用到上面的格式常量)。還有一個比較通用的轉(zhuǎn)換函數(shù)是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++中調(diào)用python函數(shù)的函數(shù),這里采用單返回值
int function_from_python(int a,int b)
{
    //初始化
    Py_Initialize();

    //定義參數(shù)
    int res;
    PyObject *pModule=NULL;
    PyObject *pFunc=NULL;
    PyObject *pArgs=NULL;
    PyObject *pResult=NULL;

    //導入被調(diào)用的py文件名
   if(!(pModule=PyImport_Import(PyString_FromString("python_called"))))
    {
        std::cout<<"get module failed!"<<std::endl;
        exit(0);
      }

    //獲得要調(diào)用的函數(shù)名
    if(!(pFunc=PyObject_GetAttrString(pModule, "add_func")))
    {
        std::cout<<"get func failed!"<<std::endl;
        exit(0);
    }

    //傳入?yún)?shù)
    pArgs=Py_BuildValue("ii",a,b);

    //執(zhí)行函數(shù)
    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 下的編譯命令同上

編譯完后運行可執(zhí)行文件

C/C++ call python function:
8
注意:
  • 被調(diào)用的python文件必須與C++編譯出來的可執(zhí)行文件放在一個目錄。
  • 可以建vs2013工程或者Qt工程或則makefile工程文件,在里面配置include和lib目錄,更方便。

方法2:調(diào)用python文件并執(zhí)行(基礎(chǔ))

還可以使用C/C++直接執(zhí)行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++中執(zhí)行python文件
void exec_python_file()
{
    //初始化
    Py_Initialize();

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

    //choose2,執(zhí)行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配置依賴項后編譯執(zhí)行。
運行結(jié)果

C/C++ call python function: the result by python func: 23
注意:
  • 同樣的py文件必須和C/C++可執(zhí)行文件在同一個目錄。
  • PyRun_SimpleString方式其實是讀一段字符串程序,可以用FILE或者fstream讀進來文本文件然后傳入也行,這樣就可以用相對目錄了。
  • 不建議用PyRun_SimpleFile的方式,因為這個API要求傳入一個FILE指針,而微軟的幾個CRT版本FILE指針的定義有了變化,因此傳入你使用VS2005編譯的FILE指針或者其它版本的FILE極有可能崩潰,如果你想安全調(diào)用,最好是自己把Python的源代碼使用和應(yīng)用程序相同的環(huán)境一起編譯出lib來使用。

方法3:使用Cpython(高級)

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

python調(diào)用C/C++
方法1:調(diào)用C/C++動態(tài)鏈接庫(基礎(chǔ))

將C/C++的程序不經(jīng)任何加工直接編譯成動態(tài)鏈接庫so或者dll,再使用python的ctypes調(diào)用即可

源代碼測試

此處僅以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語言編譯鏈接
  • 裝在動態(tài)庫的路徑可以用相對路徑
方法2:調(diào)用C/C++編寫的python擴展模塊(基礎(chǔ))

這種方法比較好,用C/C++編寫python的擴展模塊,在python程序里面import進去就可以調(diào)用接口

原代碼測試

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);
}

函數(shù)介紹:

  • 包裹函數(shù)_add_func。它負責將Python的參數(shù)轉(zhuǎn)化為C的參數(shù)(PyArg_ParseTuple),調(diào)用實際的add_function,并處理add_function的返回值,最終返回給Python環(huán)境。
  • 參數(shù)解析PyArg_ParseTuple,將python的變量解析成C/C++變量,按照ii,si,ss等格式
  • 導出表CppModuleMethods。它負責告訴Python這個模塊里有哪些函數(shù)可以被Python調(diào)用。導出表的名字可以隨便起,每一項有4個參數(shù):第一個參數(shù)是提供給Python環(huán)境的函數(shù)名稱,第二個參數(shù)是_add_function,即包裹函數(shù)。第三個參數(shù)的含義是參數(shù)變長,第四個參數(shù)是一個說明性的字符串。導出表總是以{NULL,NULL, 0,NULL}結(jié)束。
  • 導出函數(shù)initcpp_module。這個的名字不是任取的,是你的module名稱添加前綴init。導出函數(shù)中將模塊名稱與導出表進行連接。

在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系統(tǒng)下命令編譯

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:調(diào)用二進制可執(zhí)行文件(基礎(chǔ))

用python程序調(diào)用C/C++編譯的可執(zhí)行文件
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等執(zhí)行文件
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)
注意:
  • 可執(zhí)行文件放在python文件可識別的目錄,最好同一目錄
方法4:使用 SWIG(高級)

這是一個第三方的針對python的擴展包,需要些配置文件,略。


python調(diào)用C/C++
一般性地給出三個推薦:

  • ctypes,在Python調(diào)用已經(jīng)編譯打包好的C語言動態(tài)鏈接庫。
  • SWIG,通過聲明一個.i文件(語法類似.h),用額外安裝的swig命令自動生成一個C/C++與一個Python的包裝文件,省略了手寫這兩層包裝的工作。
  • Boost.Python,這是Boost項目的一部分,本質(zhì)上是對#include <Python.h>的一個包裝。開發(fā)略顯復雜,難以配置編譯。

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

在Python代碼中調(diào)用C/C++代碼
編譯運行SWIG的example代碼樣例
用SWIG向Python提供C++里STL的容器
編譯運行Boost.Python的HelloWorld


相關(guān)文章:
  1. Python與C++相互調(diào)用
  2. c 與 python相互調(diào)用
  3. python與C/C++互相調(diào)用
  4. Python與C/C++ 模塊相互調(diào)用
  5. C/C++與Python互相調(diào)用
  6. makehuamn中python與C++/c相互調(diào)用
  7. python與C#的互相調(diào)用
  8. Python與c++的相互調(diào)用(一)
  9. java和python相互調(diào)用
  10. python c/c++ 互相調(diào)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容