摘要:調(diào)用以回調(diào)函數(shù)地址為參數(shù)的函數(shù)這個(gè)主題就稍微繞一些了,也就是說在接口中,需要傳入回調(diào)函數(shù)作為參數(shù)。這個(gè)問題在中也可以解決,并且回調(diào)函數(shù)可以用定義。代碼代碼很簡單回調(diào)函數(shù)的傳入?yún)?shù)為,返回參數(shù)也是。
項(xiàng)目中要對(duì)一個(gè)用 C 編寫的 .so 庫進(jìn)行邏輯自測(cè)。這項(xiàng)工作,考慮到靈活性,我首先考慮用 Python 來完成。
研究了一些資料,采用 python 的 ctypes 來完成這項(xiàng)工作。已經(jīng)驗(yàn)證通過,本文記錄一下適配流程。驗(yàn)證采用 cpp 來設(shè)計(jì),不過暫時(shí)還沒有涉及類的內(nèi)容。以后如果需要再補(bǔ)足。
本文地址:https://segmentfault.com/a/1190000013339754
參考資料 ctypes以下資料是關(guān)于 ctypes 的,也就是本文采用的資料:
Python的學(xué)習(xí)(三十二)---- ctypes庫的使用整理
Python Ctypes 結(jié)構(gòu)體指針處理(函數(shù)參數(shù),函數(shù)返回)
ctypes庫
用Python ctypes 建立與C的介面
Python調(diào)用C/C++動(dòng)態(tài)鏈接庫的方法詳解
【轉(zhuǎn)】python中使用 C 類型的數(shù)組以及ctypes 的用法
ctypes
將函數(shù)指針轉(zhuǎn)換為可調(diào)用對(duì)象
Python Ctypes結(jié)構(gòu)體指針處理(函數(shù)參數(shù),函數(shù)返回)
Can"t install python-dev on centos 6.5
Python 3.5, ctypes: TypeError: bytes or integer address expected instead of str instance
一些 Python 本身的資料由于研究 ctypes 時(shí)我用的是 Python 2.7,后來切換到 Python 3 的時(shí)候稍微遇到一點(diǎn)適配問題,因此也順便記錄一下我切換過程中參考的一些資料:
python多線程ctrl-c退出問題
Python多線程之怎樣優(yōu)雅的響應(yīng)中斷異常(Ctrl+C)
CentOS7.2 多個(gè)python版本共存
Python 2 和 Python 3 有哪些主要區(qū)別? - 豬了個(gè)去的回答 - 知乎
關(guān)于 python ImportError: No module named 的問題
python的模塊加載和路徑查找
如何獲得Python腳本所在目錄的位置
關(guān)于python中帶下劃線的變量和函數(shù) 的意義
【變量】關(guān)于python中的下劃線
16.16. ctypes — A foreign function library for Python
其他 python 調(diào)用 C 的方法Python 調(diào)用 C 還有其他的幾個(gè)解決方案,比如 cython、SWIG 等等。但是查了不少資料沒能解決我的兩個(gè)關(guān)鍵訴求(結(jié)構(gòu)體參數(shù)和回調(diào)函數(shù)):
Python調(diào)用C
Python.h:No such file or directory
環(huán)境準(zhǔn)備 ctypes 包準(zhǔn)備使用 ctypes,需要首先安裝 python-dev 包:
Ubuntu: $ sudo apt-get install python-dev -y CentOS: $ sudo yum install python-devel -y
這里主要包含了 ctypes 包。
.so 文件準(zhǔn)備將你的 C 代碼編譯成 .so 文件。這里假設(shè)目標(biāo)文件是 libtest.so,放在工作目錄下。
基本參數(shù)函數(shù)調(diào)用首先是最簡單的函數(shù)調(diào)用,并且函數(shù)參數(shù)為基本數(shù)據(jù)類型。待調(diào)用的函數(shù)定義如下:
extern "C" int max(int a, int b) { return (a > b) ? a : b; }
這種情況下,在 Python 中的調(diào)用就很簡單了。我們需要使用 ctypes 包中的 cdll 模塊加載 .so 文件,然后就可以調(diào)用庫中的函數(shù)了。
Python 代碼如下:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- from ctypes import * so_file = cdll.LoadLibrary("./libtest.so") # 如果前文使用的是 import ctypes,則這里應(yīng)該是 ctypes.cdll.LoadLobrary(...) ret = so_file.max(22, 20) print("so_file class:", type(so_file)) print("so_file.max =", ret)
輸出:
so_file class:調(diào)用以結(jié)構(gòu)體為參數(shù)的函數(shù)so_file.max = 22
這就稍微復(fù)雜點(diǎn)了,因?yàn)?C 語言中的結(jié)構(gòu)體在 Python 中并沒有直接一一對(duì)應(yīng)。不過不用擔(dān)心,簡單而言,解決方案就是:在 Python 代碼中調(diào)用 ctypes 的類進(jìn)行 Python 化的封裝。
網(wǎng)上的代碼進(jìn)行了最簡化的演示,這里我從這一小節(jié)開始,建議讀者把一個(gè) .so 文件,封裝成 Python 模塊。這樣一來庫的包裝更加簡潔和清晰。
這里是 C 代碼的部分,主要是結(jié)構(gòu)體的聲明。用于示例的函數(shù)很簡單,只是一個(gè) print 功能而已:
typedef struct _test_struct { int integer; char * c_str; void * ptr; int array[8]; } TestStruct_st; extern "C" const char *print_test_struct(TestStruct_st *pTestSt) { if (NULL == pTestSt) { return "C -- parameter NULL"; # "C --" 打頭區(qū)分這是在 .so 里面輸出的 } printf("C -- { "); printf("C -- integer : %d ", pTestSt->integer); printf("C -- cstr : %s ", pTestSt->c_str); printf("C -- ptr : %p ", pTestSt->ptr); printf("C -- array : ["); for (int tmp = 0; tmp < 7; tmp ++) { printf("%d, ", pTestSt->array[tmp]); } printf("%d] ", pTestSt->array[7]); printf("C -- } "); return "success"; }
首先,我們要對(duì)結(jié)構(gòu)體進(jìn)行轉(zhuǎn)換:
from ctypes import * INTARRAY8 = c_int * 8 class PyTestStruct(Structure): "TestStruct_st 的 Python 版本" _fields_ = [ ("integer", c_int), ("c_str", c_char_p), ("ptr", c_void_p), ("array", INTARRAY8) ]
首先對(duì)結(jié)構(gòu)體里的 int 數(shù)組進(jìn)行了重定義,也就是 INTARRAY8。
接著,注意一下 _fields_ 的內(nèi)容:這里就是對(duì) C 數(shù)據(jù)類型的轉(zhuǎn)換。左邊是 C 的結(jié)構(gòu)成員名稱,右邊則是在 python 中聲明一下各個(gè)成員的類型。其他的一些類型請(qǐng)參見官方文檔。
此外還需要注意一下類似于 c_int, c_void_p 等等的定義是在 ctypes 中的,如果是用 impoer ctypes 的方式包含 ctypes 模塊,則應(yīng)該寫成 ctypes.c_int, ctypes.c_void_p。
第三個(gè)要注意的是:這個(gè)類必須定義為 ctypes.Structure 的子類,否則在進(jìn)行后續(xù)的函數(shù)傳遞時(shí),ctypes 由于不知道如何進(jìn)行數(shù)據(jù)類型的對(duì)應(yīng),會(huì)拋出異常
封裝 .so 函數(shù)class testdll: "用于 libtest.so 的加載,包含了 cdll 對(duì)象" def __init__(self): self.cdll = cdll.LoadLibrary("./libtest.so") # 直接加載 .so 文件。感覺更好的方式是寫成單例 return def print_test_struct(self, test_struct): func = self.cdll.print_test_struct func.restype = c_char_p func.argtypes = [POINTER(PyTestStruct)] return func(byref(test_struct)).decode()
注意最后一句 func(byref(test_struct)) 中的 byref。這個(gè)函數(shù)可以當(dāng)作是 C 中的取地址符 & 的 Python 適配。因?yàn)楹瘮?shù)參數(shù)是一個(gè)結(jié)構(gòu)體指針(地址),因此我們需要用上 byref 函數(shù)。
Python 調(diào)用直接上 Python 代碼,很短的(import 語句就不用寫了吧,讀者自行發(fā)揮就好):
test_struct = PyTestStruct() test_struct.integer = 1 test_struct.c_str = "Hello, C".encode() # Python 2.x 則不需要寫 encode test_struct.ptr = 0xFFFFFFFF test_struct.array = INTARRAY8() for i in range(0, len(test_struct.array)): j = i + 1 test_struct.array[i] = j * 10 + j so_file = testdll() test_result = so_file.print_test_struct(test_struct) print("test_result:", test_result)
執(zhí)行結(jié)果:
C -- { C -- integer : 1 C -- cstr : Hello, C C -- ptr : 0xffffffff C -- array : [11, 22, 33, 44, 55, 66, 77, 88] C -- } test_result: success
這里可以看到,結(jié)構(gòu)體參數(shù)的準(zhǔn)備還是很簡單的,就是將用 Python 適配過來之后的類中對(duì)應(yīng)名字的成員進(jìn)行賦值就好了。
注意一下在 Python 3.x 中,str 和 bytes 類型是區(qū)分開的,而 char * 對(duì)應(yīng)的是后者,因此需要進(jìn)行 encode / decode 轉(zhuǎn)換。在 Python 2.x 則不需要。
調(diào)用以回調(diào)函數(shù)地址為參數(shù)的函數(shù)這個(gè)主題就稍微繞一些了,也就是說在 C 接口中,需要傳入回調(diào)函數(shù)作為參數(shù)。這個(gè)問題在 Python 中也可以解決,并且回調(diào)函數(shù)可以用 Python 定義。
C 代碼C 代碼很簡單:回調(diào)函數(shù)的傳入?yún)?shù)為 int,返回參數(shù)也是 int。C 代碼獲取一個(gè)隨機(jī)數(shù)交給回調(diào)去處理。
extern "C" void print_given_num(int (*callback)(int)) { if (NULL == callback) { printf("C -- No number given "); } static int s_isInit = 0; if (0 == s_isInit) { s_isInit = 1; srand(time(NULL)); } int num = callback((int)rand()); printf("C -- given num by callback: %d (0x%x) ", num, num); return; }Python 封裝
這里我還是用前面的 testdll 類來封裝:
class testdll: "用于 libtest.so 的加載,包含了 cdll 對(duì)象" def __init__(self): self.cdll = cdll.LoadLibrary("./libtest.so") return def print_given_num(self, callback): self.cdll.print_given_num(callback) return testCallbackType = CFUNCTYPE(None, c_int, c_int)
最后的 testCallbackType 通過 ctypes 定義了一個(gè)回調(diào)函數(shù)類型,這個(gè)在后面的調(diào)用中需要使用
在 CFUNCTYPE 后面的第一個(gè)參數(shù)為 None,這表示回調(diào)函數(shù)的返回值類型為 void
Python 調(diào)用 回調(diào)函數(shù)準(zhǔn)備回調(diào)函數(shù)用 Python 完成,注意接受的參數(shù)和返回?cái)?shù)據(jù)類型都應(yīng)該與 .so 中的定義一致。我這里的回調(diào)函數(shù)中,將 .so 傳過來的參數(shù)取了一個(gè)最低字節(jié)返回:
def _callback(para): print("get callback req:", hex(para)) print("return:", hex(para & 0xFF)) return para & 0xFF函數(shù)調(diào)用
so_file = testdll() cb = testCallbackType(_callback) so_file.print_given_num(cb)
執(zhí)行結(jié)果:
get callback req: 0x4f770b3a return: 0x3a C -- given num by callback: 58 (0x3a)
怎么樣,是不是覺得很簡單?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/41427.html
摘要:指針和引用假設(shè)動(dòng)態(tài)庫中有函數(shù)如下第二個(gè)參數(shù)為結(jié)構(gòu)體指針,第三個(gè)參數(shù)是一個(gè)引用。我這里選擇的是然后找到,下載替換掉重編譯和輸入版本號(hào),這里實(shí)用的是為或者參考資料通過在中調(diào)用動(dòng)態(tài)鏈接庫文件厚顏無恥加上自己的博客 0x01. 使用的 npm 包 首先要安裝 node-gyp, 用來重新編譯依賴包。 npm instal -g node-gyp 然后主要用到下面三個(gè)包: node-ffi -...
摘要:由設(shè)計(jì),作為編程語言的繼承者,于年首次發(fā)布。表達(dá)式表達(dá)式是編程語言中的語法實(shí)體,可以對(duì)其進(jìn)行評(píng)估以確定其值。它是編程語言解釋和計(jì)算以產(chǎn)生值的常量變量函數(shù)和運(yùn)算符的組合。它在年年年和年被評(píng)為年度編程語言,是唯一四次獲得該獎(jiǎng)項(xiàng)的語言。 ...
摘要:最近了解了提供的一個(gè)外部函數(shù)庫它提供了語言兼容的幾種數(shù)據(jù)類型,并且可以允許調(diào)用編譯好的庫。這里是閱讀相關(guān)資料的一個(gè)記錄,內(nèi)容大部分來自官方文檔。注意,提供的接口會(huì)在不同系統(tǒng)上有出入,比如為了加載動(dòng)態(tài)鏈接庫,在上提供的是而在上提供的是和。 參考資料 https://docs.python.org/2.7/l... http://www.ibm.com/developerw... c...
閱讀 1242·2021-11-11 16:54
閱讀 889·2021-10-19 11:44
閱讀 1356·2021-09-22 15:18
閱讀 2459·2019-08-29 16:26
閱讀 2964·2019-08-29 13:57
閱讀 3109·2019-08-26 13:32
閱讀 1093·2019-08-26 11:58
閱讀 2345·2019-08-26 10:37