石器時代
在很久很久以前,想要擴充Python要使用API來包裝成Python可用的模組才可以,面對囉唆的一堆C API是一件痛苦的事,接著,進入了石器時代,人們開始使用工具,出現了SWIG、boost::python等,讓擴充Python變成一件更簡單的事情,但是即使是如此,這還是笨重的方法,產生了笨重的二進制檔,笨重的編譯工作,對於只是要將c語言的library擴充給Python使用而言這還不夠好,接著,終於有了進化,ctypes出現了,人們想說,與其做出pyd來給python使用這種多此一舉的事情,東西就在那裡,dll就在那裡,為何不能直接使用呢? ctypes的目的就是讓Python可以直接操做c語言寫的dll,這是一個大躍進,到了python2.5 ctypes還成為了標準的模組之一,這表示新的時代的到來
對ctypes的懷疑
相對於熟悉的事物,面對類似性質但是不同的事物時,總難免有多少疑惑,我在一開始也懷疑這樣能做到什麼地步? 但是在看了它的文件後我瞭解到,大部份dll使用ctypes都可以應用得很好,像是我擔心的callback,ctypes一樣可以提供方法來把python的函數包裝成c語言的callback丟給c語言的dll,運作得很好,其中我最擔心的是多緒的問題,因為有些callback是從其它thread呼叫的,因此如果沒考慮到multi-thread問題的話,callback就廢一半了,但是它一樣也考慮很週到,即使是從不同thread呼叫的python callback,也一樣可以運作得很好 舉個例子
DSPPROC = WINFUNCTYPE(None, HDSP, DWORD, c_void_p, DWORD, c_void_p)
在有了c語言函數的原形建立後,我們就可以使用它來包裝python的函數丟給c語言的dll處理
print BASS_Init(-1, 44100, 0, 0, 0)
stream = BASS_StreamCreateFile(False, 'c:\god know.mp3', 0, 0, 0)
print stream
def dspProc(handle, channel, buffer, length, user):
print handle, channel, buffer, length, user
cDspProc = DSPPROC(dspProc)
print BASS_ChannelSetDSP(stream, cDspProc, None, 0)
BASS_ChannelPlay(stream, False)
raw_input()
值得注意的是,因為Python有reference counter,也就是說,當沒有變數指向某個物件時,那個物件可能就會被回收,因此如果我們這樣寫
BASS_ChannelSetDSP(stream, DSPPROC(dspProc), None, 0)
Oops,恭喜你,你的程式很可能隨時會當掉,因為暫時的變數在被回收後,那塊記憶體就不再是c語言的函數,裡面可能塞了一些亂七八糟的東西,你的dll一呼叫那函數,遇到了些亂七八糟的東西會當掉也不是什麼奇怪的事,因此寫ctypes時,必須同時使用Python和C語言的觀點來看,舉另一個例子,如果某個C語言的API的文件上寫到,你傳進來的字串它不會copy,只會留著它的指標,在完成之前那個指標應該都還是要為有效的,這時,你就必須在python用一個變數一直保持著reference到產生出來的c語言字串記憶體空間,防止它被回收
除了callback以外,你可能會想到: “阿,那struct和union怎麼辦?” ,ctypes一樣有辦法,以下就是一個例子
class BASS_INFO(Structure):
_fields_ = [
("flags", DWORD),
("hwsize", DWORD),
("hwfree", DWORD),
("freesam", DWORD),
("free3d", DWORD),
("minrate", DWORD),
("maxrate", DWORD),
("eax", BOOL),
("minbuf", DWORD),
("dsver", DWORD),
("latency", DWORD),
("initflags", DWORD),
("speakers", DWORD),
("freq", DWORD),
]
下一個時代的進步
人們總是覺得,事情還可以更好,事情只成功一半,用手寫ctypes是件很痛苦的事情,看著c語言的header檔複製、貼上、修改的無限迴圈,再也沒有比這個還無聊的事情,電腦的發明就是要解決問題,重覆性如此高和有規則的工作不應該浪費保貴的人力,因此,下一步就是c語言header的parser,理想的狀況是,可以自動parse c語言的header然後產生相對應的python ctypes寫的binding module,如果做到這個地步代表了什麼? 這意味著,Python能用的資源,從本來的常見module一下子突然擴大到了所有的c語言寫的library,這是多麼偉大的進步
再也沒有什麼比實例更重要了
我使用ctypes的目的在於寫BASS的python binding,BASS是一套audio library,因為它沒提供Python的binding,所以我只好自己寫,在無聊的複製貼上過程中,我有試著想過寫parser來自動化完成我的工作,但是c語言的parser已經複雜到了一個地步,如果要寫出那樣的parser,會比我直接用手寫還來得麻煩,所以就沒有繼續研究
BASS binding運作得很好,有興趣的朋友可以參考看看
http://language-binding.net/pyplusplus/pyplusplus.html
very good