python使用ctypes調(diào)用C編譯dll函數(shù)方法

1 生成被調(diào)用的C dll

1.1 C文件編譯為C動態(tài)鏈接庫

  • 在windows下,需要配置visual studio的工程設(shè)置,將工程編譯為dll,細節(jié)不在這里贅述
  • gcc環(huán)境下,需要編譯為.so文件,需要修改makefile的鏈接參數(shù),這里也不再贅述

1.2 用于外部引用的C函數(shù)聲明

1.2.1 聲明用于作為dll符號給外部調(diào)用

在函數(shù)聲明加入前綴,如
__declspec(dllexport) int Fun(int a, int b)
否則在加載該dll時會提示找不到該符號

在windows下可以通過vs自帶的dumpbin工具查看可被調(diào)用符號
dumpbin /exports test.dll

1.2.2 C函數(shù)的調(diào)用規(guī)定

C函數(shù)在調(diào)用過程中關(guān)于參數(shù)傳遞和壓棧由多種規(guī)定,作為dll提供給其他程序調(diào)用時,必須明確并統(tǒng)一為同一種調(diào)用規(guī)定,否則會導(dǎo)致棧破壞,編譯器負(fù)責(zé)具體實現(xiàn)調(diào)用規(guī)定,主要有以下幾種調(diào)用規(guī)定

調(diào)用規(guī)定 聲明 編譯符號修飾 調(diào)用規(guī)則 說明
_stdcall __declspec(dllexport) int __stdcall fun(int a, int b) _fun@number 參數(shù)從右向左入棧,調(diào)用者壓棧,被調(diào)者負(fù)責(zé)彈棧 win32 API默認(rèn)調(diào)用規(guī)則
_cdecl __declspec(dllexport)int __cdecl fun(int a, int b) _fun 參數(shù)從右向左入棧,調(diào)用者負(fù)責(zé)壓棧和彈棧 C/C++默認(rèn)調(diào)用規(guī)則
_fastcall __declspec(dllexport)int __fastcall fun(int a, int b) @fun@number 寄存器和棧共同參數(shù)與參數(shù)傳遞 寄存器傳參提高性能,難以跨平臺

2 ctypes加載dll庫接口

python下調(diào)用C庫有多種方式,ctypes是其中一種比較方便的,調(diào)用時首先需要加載dll文件,根據(jù)C dll的調(diào)用規(guī)定不同需要使用不同接口,使用ctypes需要import ctypes

  • 對于stdcall的C dll
import ctypes
Objdll = ctypes.windll.LoadLibrary("dllpath") #接口1
Objdll = ctypes.WinDLL("dllpath") #接口2

以上兩種接口都是可用的

  • 對于cdecl的C dll
import ctypes
Objdll = ctypes.cdll.LoadLibrary("dllpath")  
Objdll = ctypes.CDLL("dllpath")  

對于簡單的C函數(shù),例如int add(int a, int b), 此時就可以直接調(diào)用了,如

import ctypes
Objdll = ctypes.cdll.LoadLibrary("dllpath")  
Objdll = ctypes.CDLL("dllpath")  

c = Objdll.all(1,3)
print(c) # 4

3 ctypes調(diào)用C函數(shù)參數(shù)傳遞

對于較復(fù)雜的C函數(shù)的參數(shù)情況,ctypes調(diào)用時對入?yún)⒑统霾妥鲆欢ㄌ幚?,這里分情況討論

3.1 出參為指針

  • C代碼
/* Divide two numbers */
int divide(int a, int b, int *remainder)
{
    int quot = a / b;
    *remainder = a % b;
    return quot;
 }
  • python代碼
# int divide(int, int, int *)
_divide = _mod.divide_divide.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int))_divide.restype = ctypes.c_int

def divide(x, y):
    rem = ctypes.c_int()
    quot = _divide(x, y, rem)

    return quot,rem.value

3.2 入?yún)樽址?/h2>
  • C代碼
void PrintfInfo(char *str)
{
    printf("the str is %s\n", str);
}
  • python代碼
sBuf = 'aaaaaaaaaabbbbbbbbbbbbbb'
pStr = ctypes.c_char_p( )
pStr.value = sBuf.decode() #c_char_p類型的value只接受bytes類型數(shù)據(jù)

dll.PrintInfo(pStr)

這里使用了ctypes的內(nèi)置類型c_char_p,對應(yīng)于C的char數(shù)組,更多ctypes類型與C類型對照在后面附上

3.4 入/出參為結(jié)構(gòu)體

  • C代碼

typedef struct   

{  

char words[10];  

}keywords;  



typedef struct   

{  

keywords *kws;  

unsigned int len;  

}outStruct;  

extern "C"int __declspec(dllexport) test(outStruct *o);  

int test(outStruct *o)  

{  

unsigned int i = 4;  

o->kws = (keywords *)malloc(sizeof(unsigned char) * 10 * i);  

strcpy(o->kws[0].words, "The First Data");  

strcpy(o->kws[1].words, "The Second Data");  



o->len = i;  

return 1;  

}  
  • python代碼
# C結(jié)構(gòu)體需要專門定義class對應(yīng)
class keywords(Structure):  

        _fields_ = [('words', c_char *10),]  



class outStruct(Structure):  

        _fields_ = [('kws', POINTER(keywords)),  

                    ('len', c_int),]  

o = outStruct()  

dll.test(byref(o))  



print o.kws[0].words;  

print o.kws[1].words;  

print o.len  

以上包含了幾種主要的參數(shù)傳遞情況,ctypes也提供了一個較為完整的python類型和C類型的對照,如下:


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容