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