cdll和windll的區別


Python要想調用C語言寫的動態連接庫,不僅要兼容C接口的調用習慣,還需要兼容C語言的數據類型。幸運的是ctypes庫已經做了這兩方面的工作,以便調用動態連接庫是非常方便的。在Hello World的程序里,這行代碼編寫如下:

MessageBox = windll.user32.MessageBoxW

從這行代碼的簡潔程度來看,是非常優美的。這種優美是由於ctypes庫在背后做了非常多的工作,比如windll其實是一個比較復雜的對象。在ctypes庫里,它提供了三個容易加載動態連接庫的對象:cdllwindlloledll。通過訪問這三個對象的屬性,就可以調用動態連接庫的函數了。其中cdll主要用來加載C語言調用方式(cdecl),windll主要用來加載WIN32調用方式(stdcall),而oledll使用WIN32調用方式(stdcall)且返回值是Windows里返回的HRESULT值。如果你以前沒有學習過編程,肯定沒有辦法區分cdeclstdcall,就算學習過編程,如果沒有寫過跨不同庫之間的調用,也未必知道。因為在目前IDE的開發環境下,已經全部隱藏這些的細節。但在跨語言方面調用時,就不能忽略這種細節了。那么你也許問為什么會出現這兩種調用方式,不是同一個動態連接庫嗎?對於這個問題,問得好。要回答這個問題,得從發明C語言那時候說起。在70年代,美國人丹尼斯·里奇發明了C語言,並且使用C語言編寫UNIX,由此他就成為了C語言之父和UNIX操作系統之父。由於UNIX操作系統非常高效,修改起來也很方便,是得益於使用了C語言來編寫。隨着UNIX操作系統的推廣,C語言也變成了一個流行的語言。要讓UNIX變得高效率,那么C語言的設計上,就要着眼於高效的設計。在函數調用這方面的設計,就體現了這一點。在C語言的函數調用時,需要傳送多個參數。這些參數的傳送是可以通過寄存器或者棧來傳送。那你也許問為什么不只使用寄存器這一種方式呢?由於函數調用的參數比較多,比如達到5個。並且在那時候的CPU的寄存器非常少,也滿足不了這個要求。不像目前ARMMIPSCPU,寄存器比較多,多達13個之多。這時全部使用寄存器來傳送參數是基本可以解決問題了。在當時的環境之下,設計的C語言的編譯器都是按棧的方式來傳遞函數調用的參數,這樣不但可以解決寄存器少的問題,也可以解決另外一個問題,就是可以動態地傳遞參數的個數。上面只是解決了個數的問題,那又出現了另外一個問題,就是參數的入棧的順序問題。這個好比像學校里體育老師叫一班學生來排隊,排頭是從高到矮,還是從矮到高的選擇。在入棧這個問題上,C語言也面臨兩個選擇,一個跟代碼的書寫的順序一樣從左到右,另一個是從右到左。在考慮到動態參數的問題之后,C語言的設計者采用了從右到左的入棧方式,這種方式有兩個優點:一是函數運行時,默認方式是從左到右,意味着出棧的方向應優先為棧頂的元素,這樣可以提高運行效率;二是函數參數不定時,運行時分析字符串里出現需要的參數,每出現一個參數就彈出棧一次,跟運行分析的順序一致。比如下面的函數聲明:

printf(const char *,...);

由上可見入棧的順序不同,調用的方式就不一樣。在C語言里都是采用從右向左的方式入棧,在PASCAL語言里是從左向右入棧順序的。在ctypes庫里cdllwindlloledll都是支持從右到左入棧的參數順序。

接着下來又引出來了另外一個問題,既然參數是采用入棧的方式來傳遞,那么就會出現這種情況,當棧的參數沒有使用到時,誰來清除,恢復棧的狀態。在這個問題上,在編譯器的設計者里又出現了兩種選擇:一種是傾向調用者清除,一種是傾向被調用者清除。這兩種方式在性能上沒有什么區別,只是安排清除的代碼在不同的位置上。cdll是使用調用者清除的棧的方式,而windlloledll是使用被調用者清除。這點就是它們之間的區別。因此,Python里調用動態連接庫時,一定要清楚每個函數使用的調用方式,否則程序就會出問題,重則直接死掉。cdllwindll的區別如下圖:

 

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com