利用 ctypes 模塊整合 Python 程序和 C 程序
ctypes 模塊
ctypes 是 Python 的一個(gè)標(biāo)準(zhǔn)模塊携御,它包含在 Python2.3 及以上的版本里。ctypes 是一個(gè) Python 的高級(jí)外部函數(shù)接口,它使得 Python 程序可以調(diào)用 C 語(yǔ)言編譯的靜態(tài)鏈接庫(kù)和動(dòng)態(tài)鏈接庫(kù)。運(yùn)用 ctypes 模塊玛迄,能夠在 Python 源程序中創(chuàng)建,訪問(wèn)和操作簡(jiǎn)單的或復(fù)雜的 C 語(yǔ)言數(shù)據(jù)類(lèi)型棚亩。最為重要的是 ctypes 模塊能夠在多個(gè)平臺(tái)上工作蓖议,包括 Windows,Windows CE讥蟆,Mac OS X勒虾,Linux,Solaris瘸彤,F(xiàn)reeBSD修然,OpenBSD。
接下來(lái)通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)看一下 ctypes 模塊如何整合 Python 程序和 C 程序质况。
源代碼層面上的整合
利用 Python 本身提供的 ctypes 模塊可以使 Python 語(yǔ)言和 C 語(yǔ)言在源代碼層面上進(jìn)行整合愕宋。本節(jié)介紹了如何通過(guò)使用 ctypes 庫(kù),在 Python 程序中可以定義類(lèi)似 C 語(yǔ)言的變量结榄。
下表列出了 ctypes 變量類(lèi)型中贝,C 語(yǔ)言變量類(lèi)型和 Python 語(yǔ)言變量類(lèi)型之間的關(guān)系:
表 1. ctypes,c 語(yǔ)言和 Python 語(yǔ)言變量類(lèi)型關(guān)系
表 1 中的第一列是在 ctypes 庫(kù)中定義的變量類(lèi)型臼朗,第二列是 C 語(yǔ)言定義的變量類(lèi)型邻寿,第三列是 Python 語(yǔ)言在不使用 ctypes 時(shí)定義的變量類(lèi)型。
舉例:
清單 1. ctypes 簡(jiǎn)單使用
>>> from ctypes import * # 導(dǎo)入 ctypes 庫(kù)中所有模塊
>>> i = c_int(45) # 定義一個(gè) int 型變量视哑,值為 45
>>> i.value # 打印變量的值 45
>>> i.value = 56 # 改變?cè)撟兞康闹禐?56
>>> i.value # 打印變量的新值 56
從下面的例子可以更明顯地看出 ctypes 里的變量類(lèi)型和 C 語(yǔ)言變量類(lèi)型的相似性:
清單 2. ctypes 使用 C 語(yǔ)言變量
>>> p = create_string_buffer(10) # 定義一個(gè)可變字符串變量绣否,長(zhǎng)度為 10
>>> p.raw # 初始值是全 0,即 C 語(yǔ)言中的字符串結(jié)束符’ \0 ’'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> p.value = "Student" # 字符串賦值
>>> p.raw # 后三個(gè)字符仍是’ \0 ’'Student\x00\x00\x00'
>>> p.value = "Big" # 再次賦值
>>> p.raw # 只有前三個(gè)字符被修改挡毅,第四個(gè)字符被修改為’ \0 ’'Big\x00ent\x00\x00\x00'
下面例子說(shuō)明了指針操作:
清單 3. ctypes 使用 C 語(yǔ)言指針
>>> i = c_int(999) # 定義 int 類(lèi)型變量 i蒜撮,值為 999
>>> pi = pointer(i) # 定義指針,指向變量 i
>>> pi.contents # 打印指針?biāo)傅膬?nèi)容 c_long(999)
>>> pi.contents = c_long(1000) # 通過(guò)指針改變變量 i 的值
>>> pi.contents # 打印指針?biāo)傅膬?nèi)容 c_long(1000)
下面例子說(shuō)明了結(jié)構(gòu)和數(shù)組的操作:
清單 4. ctypes 使用 C 語(yǔ)言數(shù)組和結(jié)構(gòu)體
>>> class POINT(Structure): # 定義一個(gè)結(jié)構(gòu)跪呈,內(nèi)含兩個(gè)成員變量 x段磨,y,均為 int 型 ... _fields_ = [("x", c_int), ... ("y", c_int)] ...
>>> point = POINT(2,5) # 定義一個(gè) POINT 類(lèi)型的變量庆械,初始值為 x=2, y=5
>>> print point.x, point.y # 打印變量 2 5
>>> point = POINT(y=5) # 重新定義一個(gè) POINT 類(lèi)型變量薇溃,x 取默認(rèn)值
>>> print point.x, point.y # 打印變量 0 5
>>> POINT_ARRAY = POINT * 3 # 定義 POINT_ARRAY 為 POINT 的數(shù)組類(lèi)型 # 定義一個(gè) POINT 數(shù)組,內(nèi)含三個(gè) POINT 變量
>>> pa = POINT_ARRAY(POINT(7, 7), POINT(8, 8), POINT(9, 9))
>>> for p in pa: print p.x, p.y # 打印 POINT 數(shù)組中每個(gè)成員的值 ... 7 7 8 8 9 9
Python 訪問(wèn) C 語(yǔ)言 dll
通過(guò) ctypes 模塊缭乘,Python 程序可以訪問(wèn) C 語(yǔ)言編譯的 dll沐序,本節(jié)通過(guò)一個(gè)簡(jiǎn)單的例子,Python 程序 helloworld.py 中調(diào)用 some.dll 中的 helloworld 函數(shù)堕绩,來(lái)介紹 Python 程序如何調(diào)用 windows 平臺(tái)上的 dll策幼。
導(dǎo)入動(dòng)態(tài)鏈接庫(kù)清單 ctypes 導(dǎo)入 dll
from ctypes import windll # 首先導(dǎo)入 ctypes 模塊的 windll 子模塊 somelibc = windll.LoadLibrary(some.dll) # 使用 windll 模塊的 LoadLibrary 導(dǎo)入動(dòng)態(tài)鏈接庫(kù)
訪問(wèn)動(dòng)態(tài)鏈接庫(kù)中的函數(shù)清單 ctypes 使用 dll 中的函數(shù)
somelibc. helloworld() # 這樣就可以得到 some.dll 的 helloworld 的返回值。
整個(gè) helloworld.py 是這樣的:
清單 7. Python helloworld 代碼
from ctypes import windll def callc(): # load the some.dll
somelibc = windll.LoadLibrary(some.dll)
print somelibc. helloworld()
if __name__== “__main__”: callc()
在命令行運(yùn)行 helloworld.py奴紧,在 console 上可以看到 some.dll 中 helloworld 的輸出特姐。
清單 8. Python hellpworld Windows command console 運(yùn)行輸出
C:>python C:\python\test\helloworld.py Hello World! Just a simple test.
Python 調(diào)用 C 語(yǔ)言 so
通過(guò) ctypes 模塊,Python 程序也可以訪問(wèn) C 語(yǔ)言編譯的 so 文件黍氮。與 Python 調(diào)用 C 的 dll 的方法基本相同唐含,本節(jié)通過(guò)一個(gè)簡(jiǎn)單的例子浅浮,Python 程序 helloworld.py 中調(diào)用 some.so 中的 helloworld 函數(shù),來(lái)介紹 Python 程序如何調(diào)用 linux 平臺(tái)上的 so捷枯。
導(dǎo)入動(dòng)態(tài)鏈接庫(kù)清單 ctypes 導(dǎo)入 so
from ctypes import cdll # 首先導(dǎo)入 ctypes 模塊的 cdll 子模塊滚秩,注意 linux 平臺(tái)上使用 cdll 的,而不是 windll淮捆。
somelibc = cdll.LoadLibrary(“./some.so”) # 使用 cdll 模塊的 LoadLibrary 導(dǎo)入動(dòng)態(tài)鏈接庫(kù)
訪問(wèn)動(dòng)態(tài)鏈接庫(kù)中的函數(shù)清單 ctypes 使用 so 中的函數(shù)
somelibc. helloworld() # 使用方法與 windows 平臺(tái)上是一樣的郁油。
整個(gè) helloworld.py 是這樣的:
清單 11. Python helloworld 代碼
from ctypes import cdll def callc(): # load the some.so
somelibc = cdll.LoadLibrary(some.so) print somelibc. helloworld()
if __name__== “__main__”: callc()
在命令行運(yùn)行 helloworld.py,在 linux 標(biāo)準(zhǔn)輸出上可以看到 some.so 中 helloworld 的輸出攀痊。
清單 12. Python hellpworld Linux shell 運(yùn)行輸出
[root@linux-790t] python ./helloworld.py Hello World! Just a simple test.
Python 程序和 C 程序整合實(shí)例
以下我們舉例用 Python 來(lái)實(shí)現(xiàn)一個(gè)小工具桐腌,用來(lái)實(shí)現(xiàn) hash 算法,查看文件的校驗(yàn)和(MD5,CRC,SHA1 等等)苟径。通過(guò)查看文件的校驗(yàn)和案站,可以知道文件在傳輸過(guò)程中是否被破壞或篡改。
Hash涩笤,一般翻譯做“散列”嚼吞,也有直接音譯為"哈希"的,就是把任意長(zhǎng)度的輸入(又叫做預(yù)映射蹬碧,pre-image)舱禽,通過(guò)散列算法,變換成固定長(zhǎng)度的輸出恩沽,該輸出就是散列值誊稚。這種轉(zhuǎn)換是一種壓縮映射,也就是罗心,散列值的空間通常遠(yuǎn)小于輸入的空間里伯,不同的輸入可能會(huì)散列成相同的輸出,而不可能從散列值來(lái)唯一的確定輸入值渤闷。簡(jiǎn)單的說(shuō)就是一種將任意長(zhǎng)度的消息壓縮到某一固定長(zhǎng)度的消息摘要的函數(shù)疾瓮。
由于相對(duì) C 語(yǔ)言來(lái)說(shuō),Python 的運(yùn)行效率較低飒箭,因此我們的 Python 小工具利用一個(gè)已有的 C 語(yǔ)言的動(dòng)態(tài)鏈接庫(kù) (hashtcalc.dll) 來(lái)實(shí)現(xiàn)我們的程序狼电。本例中,我們運(yùn)用 wxPython 編寫(xiě)簡(jiǎn)單的 GUI 界面弦蹂,通過(guò) python 調(diào)用 hashtcalc.dll 的接口計(jì)算文件的校驗(yàn)和肩碟,然后輸出在界面上。
架構(gòu)圖
圖 1. 工具的架構(gòu)圖
函數(shù)名:calc_CRC32
函數(shù):char* calc_CRC32(char filename);
參數(shù):文件名
返回值:字符串
說(shuō)明:該函數(shù)對(duì)輸入的文件內(nèi)容進(jìn)行計(jì)算凸椿,并且返回它的 CRC32
函數(shù)名:calc_MD5
函數(shù):char calc_MD5(char filename);
參數(shù):文件名
返回值:字符串
說(shuō)明:該函數(shù)對(duì)輸入的文件內(nèi)容進(jìn)行計(jì)算削祈,并且返回它的 MD5
函數(shù)名:calc_SHA1
函數(shù):char calc_SHA1 (char *filename);
參數(shù):文件名
返回值:字符串
說(shuō)明:該函數(shù)對(duì)輸入的文件內(nèi)容進(jìn)行計(jì)算,并且返回它的 SHA1
HashcalcAdapter 代碼
HashcalcAdapter.py 實(shí)現(xiàn)了一個(gè) python 的 class HashcalcAdapter,HashcalcAdapter 對(duì) hashtcalc.dl 的 C 語(yǔ)言接口進(jìn)行了封裝髓抑,使得其他 python 模塊可以直接通過(guò) HashcalcAdapter 使用 hashtcalc.dll 中實(shí)現(xiàn)的 hash 算法咙崎。具體的代碼如下:
清單 13. HashcalcAdapter.py 代碼
from ctypes import windll
from ctypes import *
class HashcalcAdapter(object):
def __init__(self, dllpath):
self._dllpath = dllpath self._libc = windll.LoadLibrary(self._dllpath)
def calc_CRC32(self, filename):
new_filename = c_char_p(filename)
return self._libc.calc_CRC32(new_filename) def calc_MD5(self, filename):
new_filename = c_char_p(filename)
return self._libc.calc_MD5(new_filename)
def calc_SHA1(self, filename):
new_filename = c_char_p(filename)
return self._libc.calc_SHA1(new_filename)
運(yùn)行界面
圖 2. 工具的運(yùn)行界面
轉(zhuǎn)載自:https://www.ibm.com/developerworks/cn/linux/l-cn-pythonandc/