Python 是一種用於快速開發軟體的程式設計語言,它的文法比較簡單,易於掌握,但存在執行速度慢的問題,並且在處理某些問題時存在不足,如對電腦硬體系統的訪問,對媒體檔案的訪問等。而作為軟體開發的傳統程式設計語言 C 語言,卻能在這些問題上很好地彌補 Python 語言的不足。因此,本文通過執行個體研究如何在 Python 程式中整合既有的 C 語言模組,包括用 C 語言編寫的來源程式和動態連結程式庫等,從而充分發揮 Python 語言和 C 語言各自的優勢。
概覽
背景知識介紹
Python 語言的特點
Python 作為一門程式開發語言,被越來越多地運用到快速程式開發。Python 是一種解釋型的,互動的,物件導向的程式設計語言,它包含了模組化的操作,異常處理,動態資料形態,以及類型的使用。它的文法表達優美易讀,具有很多優秀的指令碼語言的特點:解釋的,物件導向的,內建的進階資料結構,支援模組和包,支援多種平台,可擴充。而且它還支援互動式方式運行,圖形方式運行。它擁有眾多的編程介面支援各種作業系統平台以及眾多的各類函數庫,利用 C 和 C++ 可以對它進行擴充。
C 語言的特點
C 語言作為最受人們歡迎的語言之一,有廣泛的發展基礎。簡潔緊湊、靈活方便,功能強大是其特點。另外,C 語言是一門中級語言。它把進階語言的基本結構和語句與低級語言的實用性結合起來。由於可以直接存取物理地址,可以方便的對硬體進行操作。因此,很多的系統軟體都是由 C 語言編寫。
Python 語言與 C 語言的互動
為了節省軟體開發成本,軟體開發人員希望能夠縮短的軟體的開發時間,希望能夠在短時間內開發出穩定的產品。Python 功能強大,簡單易用,能夠快速開發應用軟體。但是由於 Python 自身執行速度的局限性,對效能要求比較高的模組需要使用效率更高的程式語言進行開發,例如 C 語言,系統的其他模組運用 Python 進行快速開發,最後將 C 語言開發的模組與 Python 開發的模組進行整合。在此背景下,基於 Python 語言與 C 語言的各自特點,用 C 語言來擴充現有的 Python 程式,顯得很有意義。本文首先介紹幾種常用的整合 Python 程式與 C 語言程式的方法,最後給出相應的執行個體。
利用 ctypes 模組整合 Python 程式和 C 程式
ctypes 模組
ctypes 是 Python 的一個標準模組,它包含在 Python2.3 及以上的版本裡。ctypes 是一個 Python 的進階外部函數介面,它使得 Python 程式可以調用 C 語言編譯的靜態連結庫和動態連結程式庫。運用 ctypes 模組,能夠在 Python 來源程式中建立,訪問和操作簡單的或複雜的 C 語言資料類型。最為重要的是 ctypes 模組能夠在多個平台上工作,包括 Windows,Windows CE,Mac OS X,Linux,Solaris,FreeBSD,OpenBSD。
接下來通過幾個簡單的例子來看一下 ctypes 模組如何整合 Python 程式和 C 程式。
原始碼層面上的整合
利用 Python 本身提供的 ctypes 模組可以使 Python 語言和 C 語言在原始碼層面上進行整合。本節介紹了如何通過使用 ctypes 庫,在 Python 程式中可以定義類似 C 語言的變數。
下表列出了 ctypes 變數類型,C 語言變數類型和 Python 語言變數類型之間的關係:
表 1. ctypes,c 語言和 Python 語言變數類型關係
表 1 中的第一列是在 ctypes 庫中定義的變數類型,第二列是 C 語言定義的變數類型,第三列是 Python 語言在不使用 ctypes 時定義的變數類型。
舉例:
清單 1. ctypes 簡單使用
>>> from ctypes import * # 匯入 ctypes 庫中所有模組 >>> i = c_int(45) # 定義一個 int 型變數,值為 45 >>> i.value # 列印變數的值 45 >>> i.value = 56 # 改變該變數的值為 56 >>> i.value # 列印變數的新值 56
從下面的例子可以更明顯地看出 ctypes 裡的變數類型和 C 語言變數類型的相似性:
清單 2. ctypes 使用 C 語言變數
>>> p = create_string_buffer(10) # 定義一個可變字串變數,長度為 10 >>> p.raw # 初始值是全 0,即 C 語言中的字串結束符' \0 ''\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' >>> p.value = "Student" # 字串賦值 >>> p.raw # 後三個字元仍是' \0 ''Student\x00\x00\x00' >>> p.value = "Big" # 再次賦值 >>> p.raw # 只有前三個字元被修改,第四個字元被修改為' \0 ''Big\x00ent\x00\x00\x00'
下面例子說明了指標操作:
清單 3. ctypes 使用 C 語言指標
>>> i = c_int(999) # 定義 int 類型變數 i,值為 999 >>> pi = pointer(i) # 定義指標,指向變數 i >>> pi.contents # 列印指標所指的內容 c_long(999) >>> pi.contents = c_long(1000) # 通過指標改變變數 i 的值 >>> pi.contents # 列印指標所指的內容 c_long(1000)
下面例子說明了結構和數組的操作:
清單 4. ctypes 使用 C 語言數組和結構體
>>> class POINT(Structure): # 定義一個結構,內含兩個成員變數 x,y,均為 int 型 ... _fields_ = [("x", c_int), ... ("y", c_int)] ... >>> point = POINT(2,5) # 定義一個 POINT 類型的變數,初始值為 x=2, y=5 >>> print point.x, point.y # 列印變數 2 5 >>> point = POINT(y=5) # 重新定義一個 POINT 類型變數,x 取預設值 >>> print point.x, point.y # 列印變數 0 5 >>> POINT_ARRAY = POINT * 3 # 定義 POINT_ARRAY 為 POINT 的數群組類型 # 定義一個 POINT 數組,內含三個 POINT 變數 >>> pa = POINT_ARRAY(POINT(7, 7), POINT(8, 8), POINT(9, 9)) >>> for p in pa: print p.x, p.y # 列印 POINT 數組中每個成員的值 ... 7 7 8 8 9 9
Python 訪問 C 語言 dll
通過 ctypes 模組,Python 程式可以訪問 C 語言編譯的 dll,本節通過一個簡單的例子,Python 程式 helloworld.py 中調用 some.dll 中的 helloworld 函數,來介紹 Python 程式如何調用 windows 平台上的 dll。
匯入動態連結程式庫
清單 5. ctypes 匯入 dll
from ctypes import windll # 首先匯入 ctypes 模組的 windll 子模組 somelibc = windll.LoadLibrary(some.dll) # 使用 windll 模組的 LoadLibrary 匯入動態連結程式庫
訪問動態連結程式庫中的函數
清單 6. ctypes 使用 dll 中的函數
somelibc. helloworld() # 這樣就可以得到 some.dll 的 helloworld 的傳回值。
整個 helloworld.py 是這樣的:
清單 7. Python hellpworld 代碼
from ctypes import windll def callc(): # load the some.dll somelibc = windll.LoadLibrary(some.dll) print somelibc. helloworld() if __name__== “__main__”: callc()
在命令列運行 helloworld.py,在 console 上可以看到 some.dll 中 helloworld 的輸出。
清單 8. Python hellpworld Windows command console 運行輸出
C:\>python C:\python\test\helloworld.py Hello World! Just a simple test.
Python 調用 C 語言 so
通過 ctypes 模組,Python 程式也可以訪問 C 語言編譯的 so 檔案。與 Python 調用 C 的 dll 的方法基本相同,本節通過一個簡單的例子,Python 程式 helloworld.py 中調用 some.so 中的 helloworld 函數,來介紹 Python 程式如何調用 linux 平台上的 so。
匯入動態連結程式庫
清單 9. ctypes 匯入 so
from ctypes import cdll # 首先匯入 ctypes 模組的 cdll 子模組,注意 linux 平台上使用 cdll 的,而不是 windll。 somelibc = cdll.LoadLibrary(“./some.so”) # 使用 cdll 模組的 LoadLibrary 匯入動態連結程式庫
訪問動態連結程式庫中的函數
清單 10. ctypes 使用 so 中的函數
somelibc. helloworld() # 使用方法與 windows 平台上是一樣的。
整個 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()
在命令列運行 helloworld.py,在 linux 標準輸出上可以看到 some.so 中 helloworld 的輸出。
清單 12. Python hellpworld Linux shell 運行輸出
[root@linux-790t] python ./helloworld.py Hello World! Just a simple test.
Python 程式和 C 程式整合執行個體
以下我們舉例用 Python 來實現一個小工具,用來實現 hash 演算法,查看檔案的校正和(MD5,CRC,SHA1 等等)。通過查看檔案的校正和,可以知道檔案在傳輸過程中是否被破壞或篡改。
Hash,一般翻譯做“散列”,也有直接音譯為"雜湊"的,就是把任意長度的輸入(又叫做預映射,pre-image),通過散列演算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。簡單的說就是一種將任意長度的訊息壓縮到某一固定長度的訊息摘要的函數。
由於相對 C 語言來說,Python 的運行效率較低,因此我們的 Python 小工具利用一個已有的 C 語言的動態連結程式庫 (hashtcalc.dll) 來實現我們的程式。本例中,我們運用 wxPython 編寫簡單的 GUI 介面,通過 python 調用 hashtcalc.dll 的介面計算檔案的校正和,然後輸出在介面上。
架構圖
圖 1. 工具的架構圖
hashcalc.dll 介面描述
函數名:calc_CRC32
函數:char* calc_CRC32(char *filename);
參數:檔案名稱
傳回值:字串
說明:該函數對輸入的檔案內容進行計算,並且返回它的 CRC32
函數名:calc_MD5
函數:char* calc_MD5(char *filename);
參數:檔案名稱
傳回值:字串
說明:該函數對輸入的檔案內容進行計算,並且返回它的 MD5
函數名:calc_SHA1
函數:char* calc_SHA1 (char *filename);
參數:檔案名稱
傳回值:字串
說明:該函數對輸入的檔案內容進行計算,並且返回它的 SHA1
HashcalcAdapter 代碼
HashcalcAdapter.py 實現了一個 python 的 class HashcalcAdapter,HashcalcAdapter 對 hashtcalc.dl 的 C 語言介面進行了封裝,使得其他 python 模組可以直接通過 HashcalcAdapter 使用 hashtcalc.dll 中實現的 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)
運行介面
圖 2. 工具的運行介面