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

1??簡(jiǎn)介

ctypes是一個(gè)自Python 2.5開(kāi)始引入的影晓,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)用等問(wèn)題锋谐。

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"開(kāi)頭纯陨,后接一對(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)用

首先帅矗,為方便起見(jiàn)偎肃,本文中所有的模塊導(dǎo)入均通過(guò)“*”語(yǔ)法導(dǎo)入,但在實(shí)際代碼的編寫(xiě)中浑此,強(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。

由此可見(jiàn)达布,當(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ù)類型

首先考慮如下改寫(xiě)的代碼:

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)此問(wè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)。一般情況下屡律,可以編寫(xiě)一個(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ù)組的初始值狈醉。沒(méi)有接受到參數(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屬性的問(wèn)題赡译。直接以默認(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)單起見(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涝开。

由此可見(jiàn)循帐,通過(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à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)行訪問(wè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ú)需考慮此問(wèn)題躬翁。例:

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)展開(kāi)莺丑,作為實(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屬性的問(wèn)題源内。本節(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!')

由此可見(jiàn)卿嘲,對(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類型厘擂,可以訪問(wèn)實(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中倒脓,其需要先訪問(wèn)contents屬性撑螺,得到指針指向的數(shù)據(jù),其一般為ctypes類型的實(shí)例崎弃,然后再訪問(wèn)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)用线婚。由此可見(jiàn),通過(guò)pointer函數(shù)創(chuàng)建的指針類型可直接當(dāng)做指針使用盆均。但在將指針轉(zhuǎn)換為Python數(shù)據(jù)類型時(shí)塞弊,需要先訪問(wèn)contents屬性,得到指針指向的值缀踪,由于此值是ctypes類型居砖,故還需要繼續(xù)訪問(wèn)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)存訪問(wèn)出錯(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)訪問(wèn)結(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ò)屬性訪問(wèn)的方式修改了結(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)

由此可見(jiàn)影所,在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ì)象,可訪問(wèn)其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)體指針類型可以訪問(wèn)其contents屬性灵疮,得到指針?biāo)赶虻慕Y(jié)構(gòu)體,然后可繼續(xù)訪問(wèn)結(jié)構(gòu)體的x與y屬性壳繁,得到結(jié)構(gòu)體中保存的值震捣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闹炉,隨后出現(xiàn)的幾起案子蒿赢,更是在濱河造成了極大的恐慌,老刑警劉巖渣触,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羡棵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嗅钻,警方通過(guò)查閱死者的電腦和手機(jī)皂冰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)养篓,“玉大人秃流,你說(shuō)我怎么就攤上這事×” “怎么了舶胀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碧注。 經(jīng)常有香客問(wèn)我嚣伐,道長(zhǎng),這世上最難降的妖魔是什么萍丐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任轩端,我火速辦了婚禮,結(jié)果婚禮上碉纺,老公的妹妹穿的比我還像新娘船万。我一直安慰自己,他們只是感情好骨田,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著声怔,像睡著了一般态贤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上醋火,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天悠汽,我揣著相機(jī)與錄音究飞,去河邊找鬼日麸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的签餐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼朵栖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踏志!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起宿饱,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤熏瞄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后谬以,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體强饮,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年为黎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邮丰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铭乾,死狀恐怖剪廉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情片橡,我是刑警寧澤妈经,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站捧书,受9級(jí)特大地震影響吹泡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜经瓷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一爆哑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舆吮,春花似錦揭朝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至锋恬,卻和暖如春屯换,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工彤悔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘉抓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓晕窑,卻偏偏與公主長(zhǎng)得像抑片,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杨赤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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

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