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??基于GCC的DLL/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)體中保存的值震捣。