在python中調(diào)用C/C++的三種方法
Python 是一種很好用的膠水語言,利用Python的簡潔和C++的高效,基本可以解決99%的問題了,剩下那 1% 的問題也就不是問題了,畢竟不是所有問題都可解。
一般的,Python和C++的交互分為這兩種情況:
- 用C++擴(kuò)展Python:當(dāng)一個Python項目中出現(xiàn)了性能瓶頸時,將瓶頸部分抽離出來,用C++封裝成一個Python可以調(diào)用的模塊(so庫);
- 將Python內(nèi)嵌入C++:當(dāng)一個C++項目中有部分功能預(yù)期將會經(jīng)常變更需求,期望獲得更高的靈活性時,將這部分功能用Python實現(xiàn),在C++中進(jìn)行調(diào)用。
這里討論前者,在 python 中調(diào)用 C/C++ 代碼的方法很多,這里記錄三種方法的使用。
1 C/C++ 編譯成可執(zhí)行文件,python 通過 subprocess 調(diào)用
C/C++ 代碼正常編寫,然后編譯成 exe/elf 格式的可執(zhí)行文件,Python 利用 subprocess 調(diào)用該可執(zhí)行文件即可。好處是改動小,不好是至少需要兩個進(jìn)程跑代碼,而且 C/C++ 和 Python 通訊比較麻煩。
這種方法簡單粗暴,不太好用,沒什么好說的。
2 ctypes
C/C++ 在編寫代碼的時候略微改動,然后編譯成 dll/so 格式的動態(tài)庫文件,Python 利用 ctypes 調(diào)用該庫文件即可。好處一個進(jìn)程內(nèi)運行,C/C++ 側(cè)改動小,壞處是 Python 側(cè)需適配代碼比較多。
ctypes 是 python 自帶的一個庫,可以用來調(diào)用 c/cpp 的動態(tài)鏈接庫。使用 ctypes 調(diào)用 c++ 代碼步驟如下:
- 編寫 cpp 代碼,將其編譯成動態(tài)鏈接庫(.so 或者 .dll 文件)。
- 在 python 代碼文件中導(dǎo)入 ctypes 庫,并使用 ctypes.cdll.LoadLibrary() 方法加載動態(tài)鏈接庫。
- 使用 ctypes 定義 c++ 函數(shù)的參數(shù)類型和返回值類型,并調(diào)用 c++ 函數(shù)。
2.1 編譯 C++
一個簡單的 demo:
dll.cpp
extern "C" int add(int a, int b) {
return a + b;
}
在目錄 python_call_c_cpp 下,使用 g++ 編譯 dll.cpp
g++ --shared -fPIC dll.cpp -o libadd.so
編譯完成后,在目錄下會生成一個 libadd.so 文件:

2.2 python 調(diào)用 C/C++ 庫
main.py
import ctypes
# 加載動態(tài)鏈接庫
lib = ctypes.cdll.LoadLibrary("./libadd.so")
# 定義函數(shù)參數(shù)類型和返回值類型
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_int
# 調(diào)用 C++ 函數(shù)
result = lib.add(1, 2)
print("調(diào)用C++庫的結(jié)果:" + str(result))
執(zhí)行 python3 main.py:

3 Boost.Python
Boost作為一個大寶庫,提供了我們所需要的這一功能。并且,在Boost的許多庫中,已經(jīng)默認(rèn)使用了Boost.Python,所以也算是經(jīng)過了充分的測試。
3.1 安裝
Boost的大部分功能都是以頭文件的形式提供的,無需安裝;但是也有少部分功能,需要進(jìn)行手動編譯。Boost.Python 需要進(jìn)行手動編譯。
3.2 一個簡單的 demo
用C++實現(xiàn)一個模塊,在Python中調(diào)用時,可以返回一個特定的字符串。
#include <boost/python.hpp>
char const* greet()
{
return "hello, boost";
}
BOOST_PYTHON_MODULE(hello_boostpy)
{
using namespace boost::python;
def("greet", greet);
}
將其編譯成動態(tài)鏈接庫的形式:
g++ -I /usr/include/python2.7/ -fPIC -shared -o hello_boostpy.so http://hello_boostpy.cc -lboost_python
這時可以使用ldd看看hello_boostpy.so可不可以找到libboost_python,找不到的話,需要手動將其路徑加入環(huán)境變量LD_LIBRARY_PATH中,或者用ldconfig相關(guān)的命令也可以。
在Python中使用hello_boostpy庫:
# -*- coding: utf-8 -*-
import sys
sys.path.append('.')
def test():
import hello_boostpy
return hello_boostpy.greet()
if __name__ == "__main__":
print test()接下來,我們在C++實現(xiàn)的模塊中,添加一個類,并且嘗試向C++方向傳入Python的list類型對象。
C++ 類:
#include <boost/python.hpp>
#include <vector>
#include <string>
#include <sstream>
using namespace boost::python;
struct Person
{
void set_name(std::string name) { this->name = name; }
std::string print_info();
void set_items(list& prices, list& discounts);
std::string name;
std::vector<double> item_prices;
std::vector<double> item_discounts;
};
其中,Python方的list類型,在Boost.Python中有一個對應(yīng)的實現(xiàn)boost::python::list(相應(yīng)的,dict、tuple等類型都有對應(yīng)實現(xiàn))。在set_items中,我們將會用boost::python::extract對數(shù)據(jù)類型做一個轉(zhuǎn)換。
void Person::set_items(list& prices, list& discounts)
{
for(int i = 0; i < len(prices); ++i)
{
double price = extract<double>(prices[i]);
double discount = extract<double>(discounts[i]);
item_prices.push_back(price);
item_discounts.push_back(discount);
}
}
Python模塊定義部分依舊是非常直觀的代碼:
BOOST_PYTHON_MODULE(person)
{
class_<Person>("Person")
.def("set_name", &Person::set_name)
.def("print_info", &Person::print_info)
.def("set_items", &Person::set_items)
;
}
在Python代碼中,就可以像使用一個Python定義的類一樣使用Person類了:
# -*- coding: utf-8 -*-
import sys
sys.path.append('.')
def test():
import person
p = person.Person()
p.set_name('Qie')
p.set_items([100, 123.456, 888.8], [0.3, 0.1, 0.5])
print p.print_info()
if __name__ == "__main__":
test()附c++調(diào)用Python:
將Python安裝目錄下的include和libs文件夾引入到項目中
將libs目錄下的python37.lib復(fù)制一份為python37_d.lib
1、Python腳本
def Hello():
print("Hello")
def Add(a,b):
return a+b
2、C++調(diào)用python腳本
#include <Python.h>
using namespace std;
int main()
{
Py_Initialize(); //初始化,創(chuàng)建一個Python虛擬環(huán)境
if (Py_IsInitialized())
{
PyObject* pModule = NULL;
PyObject* pFunc = NULL;
pModule = PyImport_ImportModule("test_python"); //參數(shù)為Python腳本的文件名
if (pModule)
{
pFunc = PyObject_GetAttrString(pModule, "Hello"); //獲取函數(shù)
PyEval_CallObject(pFunc, NULL); //執(zhí)行函數(shù)
}
else
{
printf("導(dǎo)入Python模塊失敗...\n");
}
}
else
{
printf("Python環(huán)境初始化失敗...\n");
}
Py_Finalize();
}
接口方法
Python3.6提供給C/C++接口函數(shù),基本都是定義pylifecycle.h,pythonrun.h,ceval.h中。
- Py_Initialize() 和 Py_Finalize()
必須先調(diào)用Py_Initialize()進(jìn)行初始化,這個API用來分配Python解釋器使用的全局資源,應(yīng)用程序結(jié)束時需要調(diào)用Py_Finalize()來關(guān)閉Python的使用環(huán)境。 - Py_IsInitialized()
用來判斷Python解釋器是否初始化成功,true為成功,false為失敗。 - PyErr_Print() & PyErr_Clear()
執(zhí)行Python出錯時,PyErr_Print()可將錯誤信息顯示出來,PyErr_Clear()將錯誤信息在Python解釋器的緩存清除。 - PyRun_SimpleString()
這個函數(shù)能夠用來執(zhí)行簡單的Python語句。 - PyEval_InitThreads()
如果使用多線程調(diào)用Python腳本,就需要在初始化Python解釋器時調(diào)用PyEval_InitThreads()來啟用線程支持(導(dǎo)致Python內(nèi)部啟用線程鎖),最好在主線程啟動時就調(diào)用。該API同時也鎖定全局解釋鎖,所以,還需要在初始化完成后需要自行釋放鎖。
如果不需要使用多線程,不建議啟用該選項,互斥鎖也會不可避免的增加系統(tǒng)開銷。
3、規(guī)范化語法
#include<Python.h> //添加python的聲明
using namespace std;
int main()
{
Py_Initialize(); //1、初始化python接口
//初始化使用的變量
PyObject* pModule = NULL;
PyObject* pFunc = NULL;
PyObject* pName = NULL;
//2、初始化python系統(tǒng)文件路徑,保證可以訪問到 .py文件
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
//3、調(diào)用python文件名。當(dāng)前的測試python文件名是test.py。在使用這個函數(shù)的時候,只需要寫文件的名稱就可以了。不用寫后綴。
pModule = PyImport_ImportModule("test");
//4、調(diào)用函數(shù)
pFunc = PyObject_GetAttrString(pModule, "AdditionFc");
//5、給python傳參數(shù)
PyObject* pArgs = PyTuple_New(2);//函數(shù)調(diào)用的參數(shù)傳遞均是以元組的形式打包的,2表示參數(shù)個數(shù)。如果AdditionFc中只有一個參數(shù)時,寫1就可以了。這里只先介紹函數(shù)必須有參數(shù)存在的情況。
PyTuple_SetItem(pArgs, 0, Py_BuildValue("i", 2)); //0:表示序號。第一個參數(shù)。
PyTuple_SetItem(pArgs, 1, Py_BuildValue("i", 4)); //1:也表示序號。第二個參數(shù)。i:表示傳入的參數(shù)類型是int類型。
//6、使用C++的python接口調(diào)用該函數(shù)
PyObject* pReturn = PyEval_CallObject(pFunc, pArgs);
//7、接收python計算好的返回值
int nResult;
PyArg_Parse(pReturn, "i", &nResult);//i表示轉(zhuǎn)換成int型變量。在這里,最需要注意的是:PyArg_Parse的最后一個參數(shù),必須加上“&”符號。
//8、結(jié)束python接口初始化
Py_Finalize();
}總結(jié)
到此這篇關(guān)于在python中調(diào)用C/C++的三種方法的文章就介紹到這了,更多相關(guān)python調(diào)用C/C++內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python requests庫參數(shù)提交的注意事項總結(jié)
這篇文章主要給大家介紹了關(guān)于Python requests庫參數(shù)提交的注意事項,文中通過示例代碼和圖片介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Python matplotlib生成圖片背景透明的示例代碼
這篇文章主要介紹了Python matplotlib生成圖片背景透明的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
利用python+ffmpeg合并B站視頻及格式轉(zhuǎn)換的實例代碼
這篇文章主要介紹了利用python+ffmpeg合并B站視頻及格式轉(zhuǎn)換的實例代碼,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
Python的Flask框架應(yīng)用調(diào)用Redis隊列數(shù)據(jù)的方法
基于python3實現(xiàn)socket文件傳輸和校驗

