Python使用ctypes實(shí)現(xiàn)與C++互相調(diào)用的實(shí)踐教程
背景
小H上次使用了pybind11來(lái)調(diào)用C++的方法,這次同樣是在項(xiàng)目中遇到了需要在py層調(diào)用C++方法的情況,現(xiàn)在對(duì)于性能的需求更加敏感,所以需要使用C++的底層方法來(lái)獲得結(jié)果(當(dāng)然也可能只是因?yàn)镃++有這個(gè)方法,直接拿來(lái)用比較方便),選擇ctypes應(yīng)該也是因?yàn)檫@個(gè)比較方便吧。
示例
在C++側(cè),我們需要實(shí)現(xiàn)一個(gè)函數(shù),然后導(dǎo)出一個(gè)動(dòng)態(tài)庫(kù)方法,這里樓主從py層傳入了一個(gè)回調(diào)函數(shù),返回了對(duì)應(yīng)的字符結(jié)果。
在py側(cè),首先需要使用ctypes.CDLL加載剛才編譯出的動(dòng)態(tài)庫(kù),聲明對(duì)應(yīng)的方法,之后實(shí)現(xiàn)一個(gè)回調(diào)函數(shù)進(jìn)行傳入。
C++動(dòng)態(tài)庫(kù)實(shí)現(xiàn)
#include <iostream>
#include <string>
// 回調(diào)類(lèi)型:void callback(int, const char*)
using CallbackType = void(*)(int, const char*);
extern "C" {
// 導(dǎo)出函數(shù):循環(huán)調(diào)用回調(diào)
void run_with_callback(CallbackType cb, int count) {
if (!cb) {
std::cerr << "[C++] callback is null" << std::endl;
return;
}
std::cout << "[C++] run_with_callback start, count = " << count << std::endl;
for (int i = 0; i < count; ++i) {
std::cout << "[C++] before calling callback, i = " << i << std::endl;
std::string message = "msg from C++ index = " + std::to_string(i);
cb(i, message.c_str()); // 回調(diào)到 Python
std::cout << "[C++] after calling callback, i = " << i << std::endl;
}
std::cout << "[C++] run_with_callback end" << std::endl;
}
} // extern "C"ps:C++ 默認(rèn)會(huì)對(duì)函數(shù)名做 name mangling(名字改編),導(dǎo)致動(dòng)態(tài)庫(kù)中的符號(hào)名變復(fù)雜;ctypes按 C ABI 去找函數(shù)名,如果不顯式用 extern "C",Python 側(cè)很難直接按名字找到
用CMake編譯成動(dòng)態(tài)庫(kù)
cmake_minimum_required(VERSION 3.10)
project(pyc_callback_demo LANGUAGES CXX)
add_library(mycallback SHARED callback_lib.cpp)
set_target_properties(mycallback PROPERTIES
OUTPUT_NAME "mycallback"
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
)構(gòu)建
mkdir -p build cd build cmake .. cmake --build .
這樣就得到了一個(gè).so文件,供py側(cè)加載。
python加載動(dòng)態(tài)庫(kù)并調(diào)用
- 用 @CALLBACK_CTYPE 裝飾 Python 函數(shù),ctypes 會(huì)把它包裝成 C 函數(shù)指針。
- 字符串參數(shù)在 Python 中收到的是 bytes,需要手動(dòng)解碼
import ctypes
import pathlib
import os
# 當(dāng)前文件所在目錄
BASE_DIR = pathlib.Path(__file__).resolve().parent
LIB_PATH = BASE_DIR / "cpp_lib" / "build" / "libmycallback.so"
if not LIB_PATH.exists():
raise FileNotFoundError(f"找不到動(dòng)態(tài)庫(kù): {LIB_PATH}. 請(qǐng)先在 cpp_lib 目錄下用 CMake 編譯生成 libmycallback.so")
# 加載動(dòng)態(tài)庫(kù)
lib = ctypes.CDLL(str(LIB_PATH))
# 定義 C 端回調(diào)類(lèi)型
# 第二個(gè)參數(shù)用 ctypes.c_char_p(C 端是 const char*)
CALLBACK_CTYPE = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
# 聲明 C 函數(shù)的簽名:void run_with_callback(CallbackType cb, int count)
lib.run_with_callback.argtypes = [CALLBACK_CTYPE, ctypes.c_int]
lib.run_with_callback.restype = None
# Python 側(cè)的回調(diào)實(shí)現(xiàn)
@CALLBACK_CTYPE
def py_callback(i: int, msg: bytes) -> None:
# msg 是 bytes,需要按合適編碼解碼,這里假設(shè) UTF-8
text = msg.decode("utf-8") if msg is not None else "<NULL>"
print(f"[Python] in callback, i = {i}, msg = {text}")
def main() -> None:
print("[Python] before calling C function")
# 調(diào)用 C++ 庫(kù)函數(shù),并把 Python 回調(diào)傳進(jìn)去
lib.run_with_callback(py_callback, 3)
print("[Python] after calling C function")
if __name__ == "__main__":
main()
ctypes與pybind11區(qū)別
ctypes:純 Python 側(cè)綁定
不需要在 C++ 里寫(xiě)任何“綁定代碼”,只要提供 C ABI 的動(dòng)態(tài)庫(kù)。
Python 側(cè)通過(guò) ctypes.CDLL、CFUNCTYPE 等描述函數(shù)簽名。
只要函數(shù)滿(mǎn)足“C 風(fēng)格接口”(例如 extern "C" + 基本類(lèi)型/指針),不在意具體是由 C 還是 C++ 實(shí)現(xiàn)。
非常適合:
- 已經(jīng)有現(xiàn)成 C 庫(kù) / C 接口的 C++ 庫(kù)
- 簡(jiǎn)單函數(shù)調(diào)用、回調(diào)、不復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
pybind11:在 C++ 側(cè)寫(xiě)綁定,生成 Python 模塊
是一個(gè)頭文件庫(kù),寫(xiě)法大致像如之前提供的示例一致
編譯后得到的是一個(gè) Python 擴(kuò)展模塊(.so),import mymodule 就像普通 Python 包一樣使用。
可以非常自然地暴露:
- C++ 類(lèi)、方法、構(gòu)造函數(shù)
- STL 容器(
std::vector,std::map等) - 智能指針、異常、枚舉等
對(duì)復(fù)雜 C++ API 的封裝能力遠(yuǎn)強(qiáng)于 ctypes。
結(jié)語(yǔ)
經(jīng)過(guò)這兩次學(xué)習(xí),讓小H更加能夠體會(huì)到在代碼的世界中,語(yǔ)言并不是孤立的,弱化了對(duì)語(yǔ)言的重視程度,學(xué)習(xí)編程更應(yīng)該將編程語(yǔ)言看作一種工具,什么時(shí)候什么情況該換就換,大家各自完成各自擅長(zhǎng)的部分,更有利于找到性能與效率的均衡點(diǎn)。
到此這篇關(guān)于Python使用ctypes實(shí)現(xiàn)與C++互相調(diào)用的實(shí)踐教程的文章就介紹到這了,更多相關(guān)Python與C++互調(diào)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Django框架 查詢(xún)Extra功能實(shí)現(xiàn)解析
這篇文章主要介紹了Django框架 查詢(xún)Extra功能實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Python數(shù)組條件過(guò)濾filter函數(shù)使用示例
數(shù)組條件過(guò)濾簡(jiǎn)潔實(shí)現(xiàn)方式,使用filter函數(shù),實(shí)現(xiàn)一個(gè)條件判斷函數(shù)即可,示例代碼如下2014-07-07
Python將Word文檔轉(zhuǎn)換為Markdown格式
Markdown作為一種輕量級(jí)標(biāo)記語(yǔ)言,以其簡(jiǎn)潔的語(yǔ)法和廣泛的兼容性,本文將介紹如何使用Python將Word文檔轉(zhuǎn)換為Markdown文件,需要的可以了解下2024-11-11
對(duì)pandas中兩種數(shù)據(jù)類(lèi)型Series和DataFrame的區(qū)別詳解
今天小編就為大家分享一篇對(duì)pandas中兩種數(shù)據(jù)類(lèi)型Series和DataFrame的區(qū)別詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-11-11
Python使用Matplotlib繪制甘特圖的實(shí)踐
甘特圖已經(jīng)發(fā)展成項(xiàng)目規(guī)劃和跟蹤的必備工具,本文主要介紹了Python使用Matplotlib繪制甘特圖的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
利用Python構(gòu)建Flutter應(yīng)用的教程詳解
Flutter在軟件研發(fā)領(lǐng)域是非常流行的,今天就讓我們深入了解一下,用?Python構(gòu)建flutter應(yīng)用程序的世界,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12

