5. 在 Windows 上構(gòu)建 C 和 C++ 擴(kuò)展?
這一章簡(jiǎn)要介紹了如何使用 Microsoft Visual C++ 創(chuàng)建 Python 的 Windows 擴(kuò)展模塊,然后再提供有關(guān)其工作機(jī)理的詳細(xì)背景信息。 這些說明材料同時(shí)適用于 Windows 程序員學(xué)習(xí)構(gòu)建 Python 擴(kuò)展以及 Unix 程序員學(xué)習(xí)如何生成在 Unix 和 Windows 上均能成功構(gòu)建的軟件。
鼓勵(lì)模塊作者使用 distutils 方式來構(gòu)建擴(kuò)展模塊,而不使用本節(jié)所描述的方式。 你仍將需要使用 C 編譯器來構(gòu)建 Python;通常為 Microsoft Visual C++。
備注
這一章提及了多個(gè)包括已編碼 Python 版本號(hào)的文件名。 這些文件名以顯示為 XY
的版本號(hào)來代表;在實(shí)踐中,'X'
將為你所使用的 Python 發(fā)布版的主版本號(hào)而 'Y'
將為次版本號(hào)。 例如,如果你所使用的是 Python 2.2.1,XY
將為 22
。
5.1. 菜譜式說明?
在 Windows 和 Unix 上構(gòu)建擴(kuò)展模塊都有兩種方式:使用 distutils
包來控制構(gòu)建過程,或者全手動(dòng)操作。 distutils 方式適用于大多數(shù)擴(kuò)展;使用 distutils
構(gòu)建和打包擴(kuò)展模塊的文檔見 分發(fā) Python 模塊(遺留版本)。 如果你發(fā)現(xiàn)你確實(shí)需要手動(dòng)操作,那么研究一下 winsound 標(biāo)準(zhǔn)庫模塊的項(xiàng)目文件可能會(huì)很有幫助。
5.2. Unix 和 Windows 之間的差異?
Unix 和 Windows 對(duì)于代碼的運(yùn)行時(shí)加載使用了完全不同的范式。 在你嘗試構(gòu)建可動(dòng)態(tài)加載的模塊之前,要先了解你所用系統(tǒng)是如何工作的。
在 Unix 中,一個(gè)共享對(duì)象 (.so
) 文件中包含將由程序來使用的代碼,也包含在程序中可被找到的函數(shù)名稱和數(shù)據(jù)。 當(dāng)文件被合并到程序中時(shí),對(duì)在文件代碼中這些函數(shù)和數(shù)據(jù)的全部引用都會(huì)被改為指向程序中函數(shù)和數(shù)據(jù)在內(nèi)存中所放置的實(shí)際位置。 這基本上是一個(gè)鏈接操作。
在 Windows 中,一個(gè)動(dòng)態(tài)鏈接庫 (.dll
) 文件中沒有懸掛的引用。 而是通過一個(gè)查找表執(zhí)行對(duì)函數(shù)或數(shù)據(jù)的訪問。 因此在運(yùn)行時(shí) DLL 代碼不必在運(yùn)行時(shí)進(jìn)行修改;相反地,代碼已經(jīng)使用了 DLL 的查找表,并且在運(yùn)行時(shí)查找表會(huì)被修改以指向特定的函數(shù)和數(shù)據(jù)。
在 Unix 中,只存在一種庫文件 (.a
),它包含來自多個(gè)對(duì)象文件 (.o
) 的代碼。 在創(chuàng)建共享對(duì)象文件 (.so
) 的鏈接階段,鏈接器可能會(huì)發(fā)現(xiàn)它不知道某個(gè)標(biāo)識(shí)符是在哪里定義的。 鏈接器將在各個(gè)庫的對(duì)象文件中查找它;如果找到了它,鏈接器將會(huì)包括來自該對(duì)象文件的所有代碼。
在 Windows 中,存在兩種庫類型,靜態(tài)庫和導(dǎo)入庫 (擴(kuò)展名都是 .lib
)。 靜態(tài)庫類似于 Unix 的 .a
文件;它包含在必要時(shí)可被包括的代碼。 導(dǎo)入庫基本上僅用于讓鏈接器能確保特定標(biāo)識(shí)符是合法的,并且將在 DLL 被加載時(shí)出現(xiàn)于程序中。 這樣鏈接器可使用來自導(dǎo)入庫的信息構(gòu)建查找表以便使用未包括在 DLL 中的標(biāo)識(shí)符。 當(dāng)一個(gè)應(yīng)用程序或 DLL 被鏈接時(shí),可能會(huì)生成一個(gè)導(dǎo)入庫,它將需要被用于應(yīng)用程序或 DLL 中未來所有依賴于這些符號(hào)的 DLL。
假設(shè)你正在編譯兩個(gè)動(dòng)態(tài)加載模塊 B 和 C,它們應(yīng)當(dāng)共享另一個(gè)代碼塊 A。 在 Unix 上,你 不應(yīng) 將 A.a
傳給鏈接器作為 B.so
和 C.so
;那會(huì)導(dǎo)致它被包括兩次,這樣 B 和 C 將分別擁有它們自己的副本。 在 Windows 上,編譯 A.dll
將同時(shí)編譯 A.lib
。 你 應(yīng)當(dāng) 將 A.lib
傳給鏈接器用于 B 和 C。 A.lib
并不包含代碼;它只包含將在運(yùn)行時(shí)被用于訪問 A 的代碼的信息。
在 Windows 上,使用導(dǎo)入庫有點(diǎn)像是使用 import spam
;它讓你可以訪問 spam 中的名稱,但并不會(huì)創(chuàng)建一個(gè)單獨(dú)副本。 在 Unix 上,鏈接到一個(gè)庫更像是 from spam import *
;它會(huì)創(chuàng)建一個(gè)單獨(dú)副本。
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.
當(dāng)在 Windows 中創(chuàng)建 DLL 時(shí),你必須將 pythonXY.lib
傳給鏈接器。 要編譯兩個(gè) DLL,spam 和 ni (會(huì)使用 spam 中找到的 C 函數(shù)),你應(yīng)當(dāng)使用以下命令:
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.dll
和 spam.lib
。 Spam.dll
不包含任何 Python 函數(shù) (例如 PyArg_ParseTuple()
),但它通過 pythonXY.lib
可以知道如何找到所需的 Python 代碼。
第二條命令創(chuàng)建了 ni.dll
(以及 .obj
和 .lib
),它知道如何從 spam 以及 Python 可執(zhí)行文件中找到所需的函數(shù)。
不是每個(gè)標(biāo)識(shí)符都會(huì)被導(dǎo)出到查找表。 如果你想要任何其他模塊(包括 Python)都能看到你的標(biāo)識(shí)符,你必須寫上 _declspec(dllexport)
,就如在 void _declspec(dllexport) initspam(void)
或 PyObject _declspec(dllexport) *NiGetSpamData(void)
中一樣。
Developer Studio 將加入大量你并不真正需要的導(dǎo)入庫,使你的可執(zhí)行文件大小增加 100K。 要擺脫它們,請(qǐng)使用項(xiàng)目設(shè)置對(duì)話框的鏈接選項(xiàng)卡指定 忽略默認(rèn)庫。 將正確的 msvcrtxx.lib
添加到庫列表中。