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,群內有免費資料供大家一起交流學習哦!!