5. 在 Windows 上構建 C 和 C++ 擴展?

這一章簡(jiǎn)要介紹了如何使用 Microsoft Visual C++ 創(chuàng )建 Python 的 Windows 擴展模塊,然后再提供有關(guān)其工作機理的詳細背景信息。 這些說(shuō)明材料同時(shí)適用于 Windows 程序員學(xué)習構建 Python 擴展以及 Unix 程序員學(xué)習如何生成在 Unix 和 Windows 上均能成功構建的軟件。

鼓勵模塊作者使用 distutils 方式來(lái)構建擴展模塊,而不使用本節所描述的方式。 你仍將需要使用 C 編譯器來(lái)構建 Python;通常為 Microsoft Visual C++。

備注

這一章提及了多個(gè)包括已編碼 Python 版本號的文件名。 這些文件名以顯示為 XY 的版本號來(lái)代表;在實(shí)踐中,'X' 將為你所使用的 Python 發(fā)布版的主版本號而 'Y' 將為次版本號。 例如,如果你所使用的是 Python 2.2.1,XY 將為 22。

5.1. 菜譜式說(shuō)明?

在 Windows 和 Unix 上構建擴展模塊都有兩種方式:使用 distutils 包來(lái)控制構建過(guò)程,或者全手動(dòng)操作。 distutils 方式適用于大多數擴展;使用 distutils 構建和打包擴展模塊的文檔見(jiàn) 分發(fā) Python 模塊(遺留版本)。 如果你發(fā)現你確實(shí)需要手動(dòng)操作,那么研究一下 winsound 標準庫模塊的項目文件可能會(huì )很有幫助。

5.2. Unix 和 Windows 之間的差異?

Unix 和 Windows 對于代碼的運行時(shí)加載使用了完全不同的范式。 在你嘗試構建可動(dòng)態(tài)加載的模塊之前,要先了解你所用系統是如何工作的。

在 Unix 中,一個(gè)共享對象 (.so) 文件中包含將由程序來(lái)使用的代碼,也包含在程序中可被找到的函數名稱(chēng)和數據。 當文件被合并到程序中時(shí),對在文件代碼中這些函數和數據的全部引用都會(huì )被改為指向程序中函數和數據在內存中所放置的實(shí)際位置。 這基本上是一個(gè)鏈接操作。

在 Windows 中,一個(gè)動(dòng)態(tài)鏈接庫 (.dll) 文件中沒(méi)有懸掛的引用。 而是通過(guò)一個(gè)查找表執行對函數或數據的訪(fǎng)問(wèn)。 因此在運行時(shí) DLL 代碼不必在運行時(shí)進(jìn)行修改;相反地,代碼已經(jīng)使用了 DLL 的查找表,并且在運行時(shí)查找表會(huì )被修改以指向特定的函數和數據。

在 Unix 中,只存在一種庫文件 (.a),它包含來(lái)自多個(gè)對象文件 (.o) 的代碼。 在創(chuàng )建共享對象文件 (.so) 的鏈接階段,鏈接器可能會(huì )發(fā)現它不知道某個(gè)標識符是在哪里定義的。 鏈接器將在各個(gè)庫的對象文件中查找它;如果找到了它,鏈接器將會(huì )包括來(lái)自該對象文件的所有代碼。

在 Windows 中,存在兩種庫類(lèi)型,靜態(tài)庫和導入庫 (擴展名都是 .lib)。 靜態(tài)庫類(lèi)似于 Unix 的 .a 文件;它包含在必要時(shí)可被包括的代碼。 導入庫基本上僅用于讓鏈接器能確保特定標識符是合法的,并且將在 DLL 被加載時(shí)出現于程序中。 這樣鏈接器可使用來(lái)自導入庫的信息構建查找表以便使用未包括在 DLL 中的標識符。 當一個(gè)應用程序或 DLL 被鏈接時(shí),可能會(huì )生成一個(gè)導入庫,它將需要被用于應用程序或 DLL 中未來(lái)所有依賴(lài)于這些符號的 DLL。

假設你正在編譯兩個(gè)動(dòng)態(tài)加載模塊 B 和 C,它們應當共享另一個(gè)代碼塊 A。 在 Unix 上,你 不應A.a 傳給鏈接器作為 B.soC.so;那會(huì )導致它被包括兩次,這樣 B 和 C 將分別擁有它們自己的副本。 在 Windows 上,編譯 A.dll 將同時(shí)編譯 A.lib。 你 應當A.lib 傳給鏈接器用于 B 和 C。 A.lib 并不包含代碼;它只包含將在運行時(shí)被用于訪(fǎng)問(wèn) A 的代碼的信息。

在 Windows 上,使用導入庫有點(diǎn)像是使用 import spam;它讓你可以訪(fǎng)問(wèn) spam 中的名稱(chēng),但并不會(huì )創(chuàng )建一個(gè)單獨副本。 在 Unix 上,鏈接到一個(gè)庫更像是 from spam import *;它會(huì )創(chuàng )建一個(gè)單獨副本。

5.3. DLL 的實(shí)際使用?

Windows Python is built in Microsoft Visual C++; using other compilers may or may not work. The rest of this section is MSVC++ specific.

當在 Windows 中創(chuàng )建 DLL 時(shí),你必須將 pythonXY.lib 傳給鏈接器。 要編譯兩個(gè) DLL,spam 和 ni (會(huì )使用 spam 中找到的 C 函數),你應當使用以下命令:

cl /LD /I/python/include spam.c ../libs/pythonXY.lib
cl /LD /I/python/include ni.c spam.lib ../libs/pythonXY.lib

第一條命令創(chuàng )建了三個(gè)文件: spam.obj, spam.dllspam.lib。 Spam.dll 不包含任何 Python 函數 (例如 PyArg_ParseTuple()),但它通過(guò) pythonXY.lib 可以知道如何找到所需的 Python 代碼。

第二條命令創(chuàng )建了 ni.dll (以及 .obj.lib),它知道如何從 spam 以及 Python 可執行文件中找到所需的函數。

不是每個(gè)標識符都會(huì )被導出到查找表。 如果你想要任何其他模塊(包括 Python)都能看到你的標識符,你必須寫(xiě)上 _declspec(dllexport),就如在 void _declspec(dllexport) initspam(void)PyObject _declspec(dllexport) *NiGetSpamData(void) 中一樣。

Developer Studio 將加入大量你并不真正需要的導入庫,使你的可執行文件大小增加 100K。 要擺脫它們,請使用項目設置對話(huà)框的鏈接選項卡指定 忽略默認庫。 將正確的 msvcrtxx.lib 添加到庫列表中。