Python ctypes模塊使用方法與心得體會(huì)

1??簡(jiǎn)介

ctypes是一個(gè)自Python 2.5開始引入的,Python自帶的函數(shù)庫(kù)。其提供了一系列與C、C++語(yǔ)言兼容的數(shù)據(jù)結(jié)構(gòu)類與方法,可基于由C源代碼編譯而來(lái)的DLL動(dòng)態(tài)鏈接庫(kù)文件,進(jìn)行Python程序與C程序之間的數(shù)據(jù)交換與相互調(diào)用。

本文基于Python 3.6.3(64 Bit)以及MinGW GCC 6.3.0(64 Bit)。

注意,在使用ctypes的過(guò)程中,Python解釋器與C編譯器所支持的位數(shù)必須一致,即:32位Python解釋器必須與32位C編譯器配合使用,同理,64位編譯器也必須保持一致使用,否則將很有可能導(dǎo)致DLL文件無(wú)法被解析、調(diào)用等問題。

2??基于GCCDLL/SO動(dòng)態(tài)鏈接庫(kù)的編譯

本文使用gcc/g++編譯器進(jìn)行C源代碼的編譯操作。當(dāng)需要使用gcc進(jìn)行dll動(dòng)態(tài)鏈接庫(kù)文件(Linux上為so文件)的編譯時(shí),可使用如下命令進(jìn)行:

>>> gcc -shared –fPIC xxx.c –oxxx.dll

上述命令中,“-shared”與“–fPIC”參數(shù)用于指示編譯器進(jìn)行動(dòng)態(tài)鏈接庫(kù)的編譯,其后接輸入文件名,一般為“.c”或“.cpp”結(jié)尾的文件。“-o”參數(shù)用于指定輸出文件名,一般為“.dll”結(jié)尾的動(dòng)態(tài)鏈接庫(kù)文件。執(zhí)行完此命令后,給定的輸出路徑下就會(huì)生成一個(gè)動(dòng)態(tài)鏈接庫(kù)文件,以供Python代碼調(diào)用。

如果將上述命令用于C++源碼的編譯,則應(yīng)將gcc編譯器換為g++,且輸入文件拓展名一般為“.cpp”。且如果當(dāng)前操作系統(tǒng)使用的是Linux,則動(dòng)態(tài)鏈接庫(kù)一般以“.so”作為拓展名。

3? ?C++源碼中的extern聲明

原理上,Python解釋器只可以直接調(diào)用C的函數(shù)接口,而對(duì)于C++的函數(shù)接口,則需要通過(guò)extern聲明轉(zhuǎn)換為C的函數(shù)接口,而后才能進(jìn)行dll編譯,以供Python調(diào)用。如果不做這樣的聲明,則將導(dǎo)致Python解釋器提示找不到目標(biāo)函數(shù)接口。C++的extern語(yǔ)句語(yǔ)法較為復(fù)雜,這里不做詳細(xì)討論,只展示本文需要用到的部分。

如果要將C++的函數(shù)接口聲明為Python可調(diào)用的版本,則需要將文件中所有的函數(shù)原型聲明放入extern聲明的代碼塊中,例:

extern "C"

{

???int add(int aNum, int bNum);

}

int add(int aNum, int bNum)

{ ... }

上述代碼中,假設(shè)我們聲明了一個(gè)函數(shù)定義add,則需要將其函數(shù)原型聲明放入extern的聲明代碼塊中。extern聲明以extern "C"開頭,后接一對(duì)花括號(hào)代碼塊,其中包含下面所有函數(shù)的函數(shù)原型聲明。

extern中也可略去原型聲明,直接在其內(nèi)部定義函數(shù),這樣做的效果類似于內(nèi)聯(lián)函數(shù)。例:

extern "C"

{

???int add(int aNum, int bNum)

??? {

???????...

??? }

}

最后,本節(jié)所討論的extern聲明的目的是將C++代碼轉(zhuǎn)變?yōu)榭杀籔ython調(diào)用的形式,而對(duì)于C語(yǔ)言源碼,則無(wú)需進(jìn)行此步驟,直接編譯即可。

4? DLL文件解析與簡(jiǎn)單函數(shù)調(diào)用

首先,為方便起見,本文中所有的模塊導(dǎo)入均通過(guò)“*”語(yǔ)法導(dǎo)入,但在實(shí)際代碼的編寫中,強(qiáng)烈不建議使用這種語(yǔ)法:

from ctypes import *

當(dāng)編譯好一個(gè)dll或so動(dòng)態(tài)庫(kù)文件后,在Python中就可通過(guò)ctypes模塊去解析與調(diào)用了。解析dll文件可調(diào)用CDLL函數(shù),其接受dll文件名作為參數(shù),返回一個(gè)解析后的實(shí)例對(duì)象:

dllObj = CDLL('1.dll')

取得此對(duì)象后,即可像調(diào)用實(shí)例方法那樣,以此對(duì)象調(diào)用dll文件中的各種函數(shù)。也就是說(shuō),dll文件中的所有函數(shù),在經(jīng)過(guò)CDLL函數(shù)解析并返回一個(gè)對(duì)象后,均可看做是此對(duì)象的實(shí)例方法,可以以點(diǎn)號(hào)的形式進(jìn)行調(diào)用。例:

假設(shè)定義了如下C源代碼,并已編譯為“1.dll”文件:

int addNum(int xNum, int yNum)

{

???return xNum + yNum;

}

而后即可在Python中傳入?yún)?shù)并調(diào)用此函數(shù):

dllObj = CDLL('1.dll')

print(dllObj.addNum(1, 2))

輸出結(jié)果為3。

由此可見,當(dāng)調(diào)用CDLL函數(shù)解析dll文件,并得到解析對(duì)象后,即可像調(diào)用實(shí)例方法那樣調(diào)用dll文件中的函數(shù),且函數(shù)的參數(shù)就是實(shí)例方法的參數(shù)。

5??基本ctypes數(shù)據(jù)類型

下表展示了ctypes模塊所提供的所有與C語(yǔ)言對(duì)應(yīng)的基本數(shù)據(jù)類型:

ctypes類型C類型Python類型

c_bool_Boolbool (1)

c_charchar1個(gè)字符的字符串

c_wcharwchar_t1個(gè)字符的unicode字符串

c_bytecharint/long

c_ubyteunsigned charint/long

c_shortshortint/long

c_ushortunsigned shortint/long

c_intintint/long

c_uintunsigned intint/long

c_longlongint/long

c_ulongunsigned longint/long

c_longlonglong longint/long

c_ulonglongunsigned long longint/long

c_floatfloatfloat

c_doubledoublefloat

c_char_pchar *string或None

c_wchar_pwchar_t *unicode或None

c_void_pvoid *int/long或None

由上表可以看出,ctypes的命名規(guī)律是前置的“c_”加上C語(yǔ)言中的數(shù)據(jù)類型名,構(gòu)成ctypes中定義的C語(yǔ)言數(shù)據(jù)類型。上表中較為常用的類型主要包括c_int、c_double、c_char以及c_char_p等。這三種類型將在下一節(jié)詳細(xì)討論。

6??函數(shù)的輸入、輸出數(shù)據(jù)類型

首先考慮如下改寫的代碼:

double addNum(double xNum, double yNum)

{

???return xNum + yNum;

}

此代碼唯一的改動(dòng)之處在于將上文的addNum函數(shù)的輸入以及輸出的數(shù)據(jù)類型均由int轉(zhuǎn)成了double。此時(shí)如果直接編譯此文件,并在Python中調(diào)用,就會(huì)發(fā)現(xiàn)程序拋出了ctypes定義的ctypes.ArgumentError異常,提示參數(shù)有錯(cuò)誤。

出現(xiàn)此問題的原因在于DLL文件無(wú)法在調(diào)用其中函數(shù)時(shí)自動(dòng)設(shè)定數(shù)據(jù)類型,而如果不對(duì)類型進(jìn)行設(shè)定,則調(diào)用函數(shù)時(shí)默認(rèn)的輸入、輸出類型均為int。故如果函數(shù)的參數(shù)或返回值包含非int類型時(shí),就需要對(duì)函數(shù)的參數(shù)以及返回值的數(shù)據(jù)類型進(jìn)行設(shè)定。

設(shè)定某個(gè)函數(shù)的參數(shù)和返回值的數(shù)據(jù)類型分別通過(guò)設(shè)定每個(gè)函數(shù)的argtypes與restype屬性實(shí)現(xiàn)。argtypes需要設(shè)定為一個(gè)tuple,其中依次給出各個(gè)參數(shù)的數(shù)據(jù)類型,這里的數(shù)據(jù)類型均指ctypes中定義的類型。同理,由于C語(yǔ)言函數(shù)只能返回一個(gè)值,故restype屬性就需要指定為單個(gè)ctypes類型。

對(duì)于上文的返回值為double的addNum,代碼修改為如下形式即可運(yùn)行:

dllObj = CDLL('1.dll')

dllObj.addNum.argtypes = (c_double,c_double)

dllObj.addNum.restype = c_double

print(dllObj.addNum(1.1, 2.2))

上述代碼在調(diào)用addNum之前,分別設(shè)定了此函數(shù)的輸入?yún)?shù)為兩個(gè)double,返回值也為double,然后以兩個(gè)小數(shù)作為參數(shù)調(diào)用這個(gè)函數(shù),返回值為3.3,結(jié)果正確。

對(duì)于一個(gè)返回值類型為char指針的C語(yǔ)言函數(shù),則只需要設(shè)定restype為c_char_p,函數(shù)即可直接返回Python的str類型至Python代碼中。例:

設(shè)有如下返回字符串指針的C語(yǔ)言函數(shù):

char *helloStr()

{

???return "Hello!";

}

則Python的調(diào)用代碼:

dllObj = CDLL('1.dll')

dllObj.addNum.restype = c_char_p

print(dllObj.helloStr())

上述代碼調(diào)用了返回字符串指針的helloStr函數(shù),并先行設(shè)定返回值類型為c_char_p,則調(diào)用后即可直接獲得一個(gè)Python字符串“Hello!”。

7??類方法的調(diào)用

對(duì)于C++中類方法的調(diào)用,可以通過(guò)多種方法實(shí)現(xiàn)。一般情況下,可以編寫一個(gè)函數(shù),其中動(dòng)態(tài)地聲明一個(gè)實(shí)例指針,然后通過(guò)指針調(diào)用某個(gè)類方法,調(diào)用完成后,再釋放此時(shí)動(dòng)態(tài)申請(qǐng)的內(nèi)存。例:

extern "C"

{

???void hello();

}

class Test

{

???public:

???????void hello();

};

void Test::hello()

{

???printf("Hello!");

}

void hello()

{

???Test *testP = new Test;

???testP -> hello();

???delete testP;

}

上述代碼定義了一個(gè)Test類,而后定義了一個(gè)Test類的hello方法,再定義了一個(gè)名為hello的函數(shù)作為接口函數(shù)。在接口函數(shù)中,首先通過(guò)new語(yǔ)句創(chuàng)建了一個(gè)實(shí)例指針,然后通過(guò)此指針調(diào)用了Test類的hello方法,調(diào)用完成后,再釋放此指針。故在Python中,只需要調(diào)用這個(gè)接口函數(shù)hello,即可實(shí)現(xiàn)調(diào)用Teat類中的hello方法。

8??高級(jí)ctypes數(shù)據(jù)類型——數(shù)組

首先,由于Python和C的數(shù)組在數(shù)據(jù)結(jié)構(gòu)上有本質(zhì)的差別,故不可以直接通過(guò)賦值等簡(jiǎn)單操作進(jìn)行數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換,而需要一種二者兼容的數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行數(shù)據(jù)的存儲(chǔ)與傳遞。

ctypes中重載了Python的乘號(hào)運(yùn)算符(本質(zhì)上是重載了ctypes數(shù)據(jù)結(jié)構(gòu)基類的__mul__方法,使其所有的子類數(shù)據(jù)結(jié)構(gòu)均適用),使得乘號(hào)變成了定義任意長(zhǎng)度數(shù)組類的方法。具體的語(yǔ)法非常簡(jiǎn)單,將一個(gè)基本的ctypes數(shù)據(jù)類型乘上一個(gè)整數(shù),即可得到一個(gè)新的類,這個(gè)類就是基于此數(shù)據(jù)類型的某個(gè)長(zhǎng)度的數(shù)組類。例:

c_int * 10??? #相當(dāng)于C中的int [10]

c_double * 5? #相當(dāng)于C中的double [5]

注意,通過(guò)將基本類型乘上一個(gè)數(shù)的方式得到的只是一個(gè)新的類,而不是這個(gè)類的實(shí)例,要得到一個(gè)真正的實(shí)例對(duì)象,就需要實(shí)例化這個(gè)類。實(shí)例化時(shí),類可接受不超過(guò)聲明長(zhǎng)度的,任意數(shù)量的參數(shù),作為數(shù)組的初始值。沒有接受到參數(shù)的部分會(huì)自動(dòng)初始化為0、None或其他布爾值為False的量,具體情況視數(shù)據(jù)類型而定,如果聲明的是整形或浮點(diǎn)型數(shù)組,那么就會(huì)初始化為0,而如果聲明的是字符串相關(guān)的量,那么就會(huì)初始化為None。本節(jié)只討論數(shù)字類型的數(shù)組,字符串相關(guān)的數(shù)組將在下文進(jìn)行討論。例:

5個(gè)0的整形數(shù)組:

(c_int * 5)()

前三個(gè)數(shù)為1-3,后續(xù)全為0的10長(zhǎng)度浮點(diǎn)型數(shù)組:

(c_double * 10)(1, 2, 3)

對(duì)于Python而言,數(shù)字類型的數(shù)組是一個(gè)可迭代對(duì)象,其可通過(guò)for循環(huán)、next方法等方式進(jìn)行迭代,以獲取其中的每一個(gè)值。例:

for i in (c_double * 10)(1, 2, 3):

???print(i)

輸出結(jié)果為1.0、2.0、3.0以及后續(xù)的7個(gè)0.0。

而對(duì)于C接口而言,這樣的實(shí)例對(duì)象就等同于在C中創(chuàng)建的數(shù)組指針,可直接作為實(shí)參傳入并修改其中的值。通過(guò)這樣的兼容數(shù)據(jù)類型,即可實(shí)現(xiàn)Python與C之間的數(shù)據(jù)傳遞。

另外,數(shù)組對(duì)象在數(shù)據(jù)類型上可看作是指針,且指針變量在ctypes中就等同于int類型,故所有涉及到指針傳遞的地方,均無(wú)需考慮修改argtypes屬性的問題。直接以默認(rèn)的int類型傳入即可。下文中也將多次使用到這一性質(zhì)。例:

C部分:

extern "C"

{

???void intList(int numList[]);

}

void intList(int numList[])

{

???for (int i = 0; i < 10; i++)

???????numList[i] = i;

}

這段代碼定義了一個(gè)intList函數(shù),作用為傳入一個(gè)整形數(shù)組指針(為簡(jiǎn)單起見,此數(shù)組假定為10個(gè)整形長(zhǎng)度),而后將數(shù)組中的值依次賦值為0-9。

Python部分:

dllObj = CDLL('1.dll')

numList = (c_int * 10)()

dllObj.intList(numList)

for i in numList:

???print(i)

這段代碼首先定義了一個(gè)數(shù)組實(shí)例numList,長(zhǎng)度為10個(gè)int,而后將這個(gè)實(shí)例像傳入數(shù)組指針一樣直接傳入dll文件中的intList函數(shù),調(diào)用完成后遍歷此數(shù)組,依次輸出每個(gè)值。結(jié)果即為在C源碼中定義的0-9。

由此可見,通過(guò)ctypes定義的數(shù)組數(shù)據(jù)類型是一種同時(shí)兼容Python與C的數(shù)據(jù)結(jié)構(gòu),對(duì)于Python而言,這種數(shù)據(jù)結(jié)構(gòu)是一個(gè)可迭代對(duì)象,可通過(guò)for循環(huán)進(jìn)行遍歷求值,而對(duì)于C而言,這樣的數(shù)組結(jié)構(gòu)就等同于在C中聲明的數(shù)組指針。故可以將Python中定義的數(shù)組傳入C中進(jìn)行運(yùn)算,最后在Python中讀取此數(shù)組的運(yùn)算結(jié)果,從而實(shí)現(xiàn)了數(shù)組類型的數(shù)據(jù)交換。

9??高級(jí)ctypes數(shù)據(jù)類型——高維數(shù)組

高維數(shù)組與一維數(shù)組在語(yǔ)法上大體類似,但在字符串?dāng)?shù)組上略有不同。本節(jié)首先討論數(shù)字類型的高維數(shù)組。此外,為簡(jiǎn)單起見,本節(jié)全部?jī)?nèi)容均對(duì)二維數(shù)組進(jìn)行討論,更高維度的數(shù)組在語(yǔ)法上與二維數(shù)組是相似的,這里不再贅述。

高維數(shù)組類可簡(jiǎn)單的通過(guò)在一維數(shù)組類外部再乘上一個(gè)數(shù)字實(shí)現(xiàn):

(c_int * 4) * 3

這樣即得到了一個(gè)4 * 3的二維int類型數(shù)組的類。

二維數(shù)組類的實(shí)例化與初始化可看作是多個(gè)一維數(shù)組初始化的疊加,可通過(guò)一個(gè)二維tuple實(shí)現(xiàn),且如果不給出任何初始化值(即一對(duì)空括號(hào)),則其中所有元素將被初始化為0。例:

((c_int * 4) * 3)()

這樣就得到了一個(gè)所有值均為0的二維數(shù)組對(duì)象。又例:

((c_int * 4) * 3)((1, 2, 3, 4), (5, 6))

上述代碼只實(shí)例化了第一個(gè)一維數(shù)組的全部以及第二個(gè)一維數(shù)組的前兩個(gè)值,而其他所有值均為0。

二維數(shù)組在使用時(shí)與一維數(shù)組一致,其可直接作為指針參數(shù)傳入C的函數(shù)接口進(jìn)行訪問,在C語(yǔ)言內(nèi)部其等同于C語(yǔ)言中聲明的二維數(shù)組。而對(duì)于Python,這樣的數(shù)組對(duì)象可通過(guò)雙層的for循環(huán)去迭代獲取每個(gè)數(shù)值。

10??高級(jí)ctypes數(shù)據(jù)類型——字符串?dāng)?shù)組

字符串?dāng)?shù)組在ctypes中的行為更接近于C語(yǔ)言中的字符串?dāng)?shù)組,其需要采用二維數(shù)組的形式來(lái)實(shí)現(xiàn),而不是Python中的一維數(shù)組。首先,需要通過(guò)c_char類型乘上一個(gè)數(shù),得到一個(gè)字符串類型,而后將此類型再乘上一個(gè)數(shù),就能得到可以包含多個(gè)字符串的字符串?dāng)?shù)組。例:

((c_char * 10) * 3)()

上例即實(shí)例化了一個(gè)3字符串?dāng)?shù)組,每個(gè)字符串最大長(zhǎng)度為10。

對(duì)于C語(yǔ)言而言,上述的字符串?dāng)?shù)組實(shí)例可直接當(dāng)做字符串指針傳入C函數(shù),其行為等同于在C中聲明的char (*)[10]指針。下詳細(xì)討論P(yáng)ython中對(duì)此對(duì)象的處理。

首先,字符串?dāng)?shù)組也是可迭代對(duì)象,可通過(guò)for循環(huán)迭代取值,對(duì)于上例的對(duì)象,其for循環(huán)得到的每一個(gè)值,都是一個(gè)10個(gè)長(zhǎng)度的字符串對(duì)象。這樣的字符串對(duì)象有兩個(gè)重要屬性:value和raw。value屬性得到是普通字符串,即忽略了字符串終止符號(hào)(即C中的\0)以后的所有內(nèi)容的字符串,而raw字符串得到的是當(dāng)前對(duì)象的全部字符集合,包括終止符號(hào)。也就是說(shuō),對(duì)于10個(gè)長(zhǎng)度的字符串對(duì)象,其raw的結(jié)果就一定是一個(gè)10個(gè)長(zhǎng)度的字符串。例:

for i in ((c_char * 10) * 3)():

???print(i.value)

???print(i.raw)

上述代碼中,i.value的輸出全為空字符串(b''),而對(duì)于i.raw,其輸出則為b'\x00\x00...',總共十個(gè)\x00。也就是說(shuō),value會(huì)忽略字符串終止符號(hào)后的所有字符,是最常用的取值方式,而raw得到不忽略終止字符的字符串。

接下來(lái)討論ctypes中對(duì)字符串對(duì)象的賦值方法。由于ctypes的字符串對(duì)象通過(guò)某個(gè)固定長(zhǎng)度的字符串類實(shí)例化得到,故在賦值時(shí),這樣的字符串對(duì)象只可以接受等同于其聲明長(zhǎng)度的字符串對(duì)象作為替代值,這是普通Python字符串做不到的。要得到這樣的定長(zhǎng)字符串,需要用到ctypes的create_string_buffer函數(shù)。

create_string_buffer函數(shù)用于創(chuàng)建固定長(zhǎng)度的帶緩沖字符串。其接受兩個(gè)參數(shù),第一參數(shù)為字符串,第二參數(shù)為目標(biāo)長(zhǎng)度,返回值即為被創(chuàng)建的定長(zhǎng)度字符串對(duì)象,可以賦值給字符串?dāng)?shù)組中的某個(gè)對(duì)象。注意,create_string_buffer函數(shù)必須接受字節(jié)字符串作為其第一參數(shù),在Python2中,普通的字符串就是字節(jié)字符串,而在Python3中,所有的字符串默認(rèn)為Unicode字符串,故可以通過(guò)字符串的encode、decode方法進(jìn)行編碼方式的轉(zhuǎn)化。encode方法可將Python3的str轉(zhuǎn)為bytes,其中的encoding參數(shù)默認(rèn)就是UTF-8,故無(wú)需給出任何參數(shù)即可調(diào)用。同理,bytes可通過(guò)decode方法,以默認(rèn)參數(shù)將bytes轉(zhuǎn)化為Python3的str,對(duì)于Python2而言,無(wú)需考慮此問題。例:

charList = ((c_char * 10) * 3)()

strList = ['aaa', 'bbb', 'ccc']

for i in range(3):

???charList[i] = create_string_buffer(strList[i].encode(), 10)

for i in charList:

???print(i.value)

上述代碼的核心在于,通過(guò)create_string_buffer函數(shù)創(chuàng)建了一個(gè)10長(zhǎng)度的帶緩沖字符串,其第二參數(shù)10用作指定長(zhǎng)度,而其第一參數(shù)為一個(gè)通過(guò)encode方法轉(zhuǎn)化成的bytes字符串,這樣得到的對(duì)象即可賦值給一個(gè)10長(zhǎng)度的字符串對(duì)象。注意,通過(guò)create_string_buffer函數(shù)創(chuàng)建的字符串對(duì)象,其長(zhǎng)度必須嚴(yán)格等同于被賦值的字符串對(duì)象的聲明長(zhǎng)度,即如果聲明的是10長(zhǎng)度字符串,那么create_string_buffer的第二參數(shù)就必須也是10,否則代碼將拋出TypeError異常,提示出現(xiàn)了類型不一致。

在字符串?dāng)?shù)組的初始化過(guò)程中,這樣的字符串對(duì)象也可作為初始化的參數(shù)。例:

strList = ['aaa', 'bbb', 'ccc']

charList = ((c_char * 10) *3)(*[create_string_buffer(i.encode(), 10) for i in strList])

for i in charList:

???print(i.value.decode())

上述代碼將實(shí)例化與初始化合并,通過(guò)列表推導(dǎo)式得到了3個(gè)10長(zhǎng)度的緩沖字符串,并使用星號(hào)展開,作為實(shí)例化的參數(shù)。則這樣得到的charList效果等同于上例中通過(guò)依次賦值得到的字符串?dāng)?shù)組對(duì)象。最后通過(guò)for循環(huán)輸出字符串對(duì)象的value屬性(一個(gè)bytes字符串),且通過(guò)decode方法將bytes轉(zhuǎn)化為str。

11??高級(jí)ctypes數(shù)據(jù)類型——指針

上文已經(jīng)討論了Python中的數(shù)組指針,而根據(jù)指針在Python中的定義,其本質(zhì)上就是一個(gè)int類型的值,所以在傳參時(shí)無(wú)需考慮修改argtypes屬性的問題。本節(jié)主要討論單個(gè)數(shù)字類型的指針。

首先,對(duì)于單個(gè)字符串,其不需要通過(guò)指針指針轉(zhuǎn)換即可當(dāng)做指針傳遞。例:

void printStr(char *str)

{

???printf("%s", str);

}

則Python中:

dllObj = CDLL('1.dll')

dllObj.printStr('Hello!')

由此可見,對(duì)于單個(gè)字符串傳進(jìn)dll,則直接通過(guò)字符串傳遞即可,傳遞過(guò)程中字符串會(huì)自動(dòng)被轉(zhuǎn)化為指針。而對(duì)于返回單個(gè)字符串的C函數(shù),上文已經(jīng)討論過(guò),通過(guò)修改restype屬性為c_char_p后,即可在Python中直接接收字符串返回值。

對(duì)于單個(gè)數(shù)值的指針,則需要通過(guò)byref或者pointer函數(shù)取得。首先考慮如下函數(shù):

void swapNum(int *a, int *b)

{

???int temp = *a;

???*a = *b;

???*b = temp;

}

此函數(shù)接收兩個(gè)int類型指針,并在函數(shù)內(nèi)部交換指針?biāo)谖恢玫恼沃怠4藭r(shí)如果通過(guò)Python傳入這兩個(gè)指針參數(shù),就需要使用到byref或者pointer函數(shù)。byref函數(shù)類似于C語(yǔ)言中的取地址符號(hào)&,其直接返回當(dāng)前參數(shù)的地址,而pointer函數(shù)更為高級(jí),其返回一個(gè)POINTER指針類型,一般來(lái)說(shuō),如果不需要對(duì)指針進(jìn)行其他額外處理,推薦直接調(diào)用byref函數(shù)獲取指針,這是較pointer更加快速的方法。此外,這兩個(gè)函數(shù)的參數(shù)都必須是ctypes類型值,可通過(guò)ctypes類型實(shí)例化得到,不可直接使用Python內(nèi)部的數(shù)值類型。例:

dllObj = CDLL('1.dll')

a, b = c_int(1), c_int(2)

dllObj.swapNum(byref(a), byref(b))

以上代碼首先通過(guò)c_int類型實(shí)例化得到了兩個(gè)c_int實(shí)例,其值分別為1和2。然后調(diào)用上文中的swapNum函數(shù),傳入的實(shí)際參數(shù)為byref函數(shù)的返回指針。這樣就相當(dāng)于在C語(yǔ)言中進(jìn)行形如swapNum(&a, &b)的調(diào)用。

要將c_int類型再轉(zhuǎn)回Python類型,可以訪問實(shí)例對(duì)象的value屬性:

print(a.value, b.value)

value屬性得到的就是Python的數(shù)字類型,而經(jīng)過(guò)上述的傳遞指針的函數(shù)調(diào)用,此時(shí)的輸出應(yīng)為2 1。

對(duì)于pointer函數(shù),其同樣接受一個(gè)ctypes實(shí)例作為參數(shù),并返回一個(gè)POINTER指針類型,而不是簡(jiǎn)單的一個(gè)指針。POINTER指針類型在傳參時(shí)也可直接作為指針傳入C語(yǔ)言函數(shù),但在Python中,其需要先訪問contents屬性,得到指針指向的數(shù)據(jù),其一般為ctypes類型的實(shí)例,然后再訪問value屬性,得到實(shí)例所對(duì)應(yīng)的Python類型的數(shù)據(jù)。例:

a, b = pointer(c_int(1)), pointer(c_int(2))

print(a.contents.value, b.contents.value)

dllObj.swapNum(a, b)

print(a.contents.value, b.contents.value)

以上代碼通過(guò)pointer函數(shù)創(chuàng)建了兩個(gè)指針類型變量,并將這兩個(gè)指針作為參數(shù)傳入函數(shù)進(jìn)行調(diào)用。由此可見,通過(guò)pointer函數(shù)創(chuàng)建的指針類型可直接當(dāng)做指針使用。但在將指針轉(zhuǎn)換為Python數(shù)據(jù)類型時(shí),需要先訪問contents屬性,得到指針指向的值,由于此值是ctypes類型,故還需要繼續(xù)訪問value屬性,得到Python的數(shù)值類型。

12??高級(jí)ctypes數(shù)據(jù)類型——結(jié)構(gòu)體

結(jié)構(gòu)體在ctypes中通過(guò)類進(jìn)行定義。用于定義結(jié)構(gòu)體的類需要繼承自ctypes的Structure基類,而后通過(guò)定義類的_fields_屬性來(lái)定義結(jié)構(gòu)體的構(gòu)成。_fields_屬性一般定義為一個(gè)二維的tuple,而對(duì)于其中的每一個(gè)一維tuple,其需要定義兩個(gè)值,第一個(gè)值為一個(gè)字符串,用作結(jié)構(gòu)體內(nèi)部的變量名,第二個(gè)值為一個(gè)ctypes類型,用于定義當(dāng)前結(jié)構(gòu)體變量所定義的數(shù)據(jù)類型。注意,在Python中定義的結(jié)構(gòu)體,其變量名,類名等均可以不同于C語(yǔ)言中的變量名,但結(jié)構(gòu)體變量的數(shù)量、數(shù)據(jù)類型與順序必須嚴(yán)格對(duì)應(yīng)于C源碼中的定義,否則可能將導(dǎo)致內(nèi)存訪問出錯(cuò)。例:

class TestStruct(Structure):

???_fields_ = (

???????('x', c_int),

???????('y', c_double),

??? )

以上代碼即定義了一個(gè)結(jié)構(gòu)體類型,其等同于C中的struct聲明。此結(jié)構(gòu)體定義了兩個(gè)結(jié)構(gòu)體變量:x對(duì)應(yīng)于一個(gè)int類型,y對(duì)應(yīng)于一個(gè)double類型。

結(jié)構(gòu)體類型可以通過(guò)實(shí)例化得到一個(gè)結(jié)構(gòu)對(duì)象,在實(shí)例化的同時(shí)也可傳入初始化參數(shù),作為結(jié)構(gòu)變量的值。在得到結(jié)構(gòu)對(duì)象后,也可通過(guò)點(diǎn)號(hào)訪問結(jié)構(gòu)體成員變量,從而對(duì)其賦值。例:

testStruct = TestStruct(1, 2)

print(testStruct.x, testStruct.y)

testStruct.x, testStruct.y = 10, 20

print(testStruct.x, testStruct.y)

上述代碼通過(guò)實(shí)例化TestStruct類,并為其提供初始化參數(shù),得到了一個(gè)結(jié)構(gòu)體實(shí)例,第一次輸出結(jié)果即為1 2.0。而后,再通過(guò)屬性訪問的方式修改了結(jié)構(gòu)體中的兩個(gè)變量,則第二次輸出結(jié)果為10 20.0。

上面定義的結(jié)構(gòu)體可直接傳入C代碼中,且上文已經(jīng)提到,兩邊定義的結(jié)構(gòu)體變量的各種名稱均可不同,但數(shù)據(jù)類型、數(shù)量與順序必須一致。例:

struct TestStruct

{

???int a;

???double b;

}

extern "C"

{

???void printStruct(TestStruct testStruct);

}

void printStruct(TestStruct testStruct)

{

???printf("%d %f\n", testStruct.a, testStruct.b);

}

Python部分:

dllObj = CDLL('1.dll')

class TestStruct(Structure):

???_fields_ = (

???????('x', c_int),

???????('y', c_double),

??? )

testStruct = TestStruct(1, 2)

dllObj.printStruct(testStruct)

由此可見,在Python中實(shí)例化得到的結(jié)構(gòu)體實(shí)例,可以直接當(dāng)做C中的結(jié)構(gòu)體實(shí)參傳入。

結(jié)構(gòu)體也可以指針的方式傳入,通過(guò)上節(jié)介紹的byref或者pointer函數(shù)即可實(shí)現(xiàn)轉(zhuǎn)化。同樣的,這兩個(gè)函數(shù)都可直接接受結(jié)構(gòu)體實(shí)例作為參數(shù)進(jìn)行轉(zhuǎn)化,byref返回簡(jiǎn)單指針,而pointer返回指針對(duì)象,可訪問其contents屬性得到指針?biāo)赶虻闹怠@?/p>

C部分,上述printStruct函數(shù)修改為接受結(jié)構(gòu)體指針的版本:

void printStruct(TestStruct *testStruct)

{

???printf("%d %f\n", testStruct -> a, testStruct -> b);

}

Python部分:

testStruct = TestStruct(1, 2)

dllObj.printStruct(byref(testStruct))

上述代碼將結(jié)構(gòu)體對(duì)象testStruct作為byref的參數(shù),從而將其轉(zhuǎn)換為指針傳入printStruct函數(shù)中。又例:

testStruct = pointer(TestStruct(1, 2))

dllObj.printStruct(testStruct)

print(testStruct.contents.x,testStruct.contents.y)

上述代碼通過(guò)結(jié)構(gòu)體對(duì)象生成了一個(gè)指針類型,并將此指針傳入函數(shù),可達(dá)到同樣的效果。且在Python內(nèi)部,結(jié)構(gòu)體指針類型可以訪問其contents屬性,得到指針?biāo)赶虻慕Y(jié)構(gòu)體,然后可繼續(xù)訪問結(jié)構(gòu)體的x與y屬性,得到結(jié)構(gòu)體中保存的值。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 指針是C語(yǔ)言中廣泛使用的一種數(shù)據(jù)類型。 運(yùn)用指針編程是C語(yǔ)言最主要的風(fēng)格之一。利用指針變量可以表示各種數(shù)據(jù)結(jié)構(gòu); ...
    朱森閱讀 3,470評(píng)論 3 44
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,606評(píng)論 1 118
  • 接上>> "怎么飯成了藥了?"我好奇寶寶似的問。 "你們看饑餓時(shí)是不是跟...
    追蜻蜓的小孩閱讀 344評(píng)論 2 2
  • 1. 在每個(gè)用戶主文件夾下有一個(gè)名為.subversion的隱藏文件夾,打開里面的config文件。 可以打開終端...
    LiJinliang閱讀 1,382評(píng)論 0 1
  • 2018年6月11日星期一晴 日記82天 兒子寫字慢,一直是非常領(lǐng)我鬧心的事情。今天聽了育兒專家的講座,讓我受益匪...
    e8fc8b84a5c1閱讀 209評(píng)論 0 0