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

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

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

備注

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

5.1. 菜譜式說明?

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

5.2. Unix 和 Windows 之間的差異?

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

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

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

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

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

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

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

5.3. DLL 的實際使用?

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 時,你必須將 pythonXY.lib 傳給鏈接器。 要編譯兩個 DLL,spam 和 ni (會使用 spam 中找到的 C 函數(shù)),你應當使用以下命令:

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

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

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

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

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