5. 在 Windows 上構建 C 和 C++ 擴充套件¶
本章簡要解釋瞭如何使用 Microsoft Visual C++ 為 Python 建立 Windows 擴充套件模組,然後提供了關於其工作原理的更詳細的背景資訊。解釋性材料對於學習構建 Python 擴充套件的 Windows 程式設計師和有興趣編寫可在 Unix 和 Windows 上成功構建的軟體的 Unix 程式設計師都很有用。
鼓勵模組作者使用 distutils 方法構建擴充套件模組,而不是本節中描述的方法。您仍然需要用於構建 Python 的 C 編譯器;通常是 Microsoft Visual C++。
備註
本章提到了許多檔名,其中包含編碼的 Python 版本號。這些檔名以版本號 XY
表示;實際上,'X'
將是您正在使用的 Python 版本的 主要版本號,'Y'
將是 次要版本號。例如,如果您使用的是 Python 2.2.1,XY
實際上將是 22
。
5.1. 簡易指南¶
在 Windows 上構建擴充套件模組有兩種方法,就像在 Unix 上一樣:使用 setuptools
包來控制構建過程,或者手動操作。setuptools 方法適用於大多數擴充套件;關於使用 setuptools
構建和打包擴充套件模組的文件可在 使用 setuptools 構建 C 和 C++ 擴充套件 中找到。如果您發現確實需要手動操作,研究標準庫模組 winsound 的專案檔案可能會有所啟發。
5.2. Unix 和 Windows 之間的差異¶
Unix 和 Windows 對程式碼的執行時載入使用完全不同的範例。在嘗試構建可以動態載入的模組之前,請了解您的系統如何工作。
在 Unix 中,共享物件(.so
)檔案包含程式使用的程式碼,以及它期望在程式中找到的函式和資料的名稱。當檔案與程式結合時,檔案中所有對這些函式和資料的引用都會更改,以指向程式中函式和資料在記憶體中的實際位置。這基本上是一個連結操作。
在 Windows 中,動態連結庫(.dll
)檔案沒有懸空的引用。相反,對函式或資料的訪問是透過查詢表進行的。因此,DLL 程式碼在執行時無需修復以引用程式的記憶體;相反,程式碼已經使用 DLL 的查詢表,並且查詢表在執行時被修改以指向函式和資料。
在 Unix 中,只有一種型別的庫檔案(.a
),它包含來自多個目標檔案(.o
)的程式碼。在建立共享物件檔案(.so
)的連結步驟中,連結器可能會發現它不知道某個識別符號在哪裡定義。連結器會在庫中的目標檔案中查詢它;如果找到,它將包含該目標檔案中的所有程式碼。
在 Windows 中,有兩種型別的庫:靜態庫和匯入庫(都稱為 .lib
)。靜態庫類似於 Unix .a
檔案;它包含根據需要包含的程式碼。匯入庫基本上只用於向連結器保證某個識別符號是合法的,並且在載入 DLL 時將存在於程式中。因此,連結器使用匯入庫中的資訊來構建查詢表,以便使用不包含在 DLL 中的識別符號。當應用程式或 DLL 連結時,可能會生成一個匯入庫,所有依賴於應用程式或 DLL 中符號的未來 DLL 都需要使用該匯入庫。
假設您正在構建兩個動態載入模組 B 和 C,它們應該共享另一塊程式碼 A。在 Unix 上,您 不會 將 A.a
傳遞給 B.so
和 C.so
的連結器;這會導致它被包含兩次,因此 B 和 C 將各自擁有自己的副本。在 Windows 中,構建 A.dll
也會構建 A.lib
。您 確實 將 A.lib
傳遞給 B 和 C 的連結器。A.lib
不包含程式碼;它只包含將在執行時用於訪問 A 程式碼的資訊。
在 Windows 中,使用匯入庫有點像使用 import spam
;它讓您可以訪問 spam 的名稱,但不會建立單獨的副本。在 Unix 中,與庫連結更像是 from spam import *
;它確實會建立單獨的副本。
-
Py_NO_LINK_LIB¶
關閉 CPython 標頭檔案中執行的與 Python 庫的隱式、基於
#pragma
的連結。在 3.14 版本加入。
5.3. DLLs 的實際使用¶
Windows Python 是用 Microsoft Visual C++ 構建的;使用其他編譯器可能有效也可能無效。本節的其餘部分是 MSVC++ 特有的。
在 Windows 中建立 DLL 時,您可以透過兩種方式使用 CPython 庫:
預設情況下,直接或透過
Python.h
包含PC/pyconfig.h
會觸發與庫的隱式、配置感知連結。標頭檔案為 Debug 選擇pythonXY_d.lib
,為 Release 選擇pythonXY.lib
,以及為啟用了 有限 API 的 Release 選擇pythonX.lib
。要構建兩個 DLL,spam 和 ni(使用 spam 中的 C 函式),您可以使用以下命令:
cl /LD /I/python/include spam.c cl /LD /I/python/include ni.c spam.lib
第一個命令建立了三個檔案:
spam.obj
、spam.dll
和spam.lib
。Spam.dll
不包含任何 Python 函式(例如PyArg_ParseTuple()
),但由於隱式連結的pythonXY.lib
,它知道如何找到 Python 程式碼。第二個命令建立了
ni.dll
(以及.obj
和.lib
),它知道如何從 spam 以及 Python 可執行檔案中找到必要的函式。透過在包含
Python.h
之前定義Py_NO_LINK_LIB
宏手動連結。您必須將pythonXY.lib
傳遞給連結器。要構建兩個 DLL,spam 和 ni(使用 spam 中的 C 函式),您可以使用以下命令:
cl /LD /DPy_NO_LINK_LIB /I/python/include spam.c ../libs/pythonXY.lib cl /LD /DPy_NO_LINK_LIB /I/python/include ni.c spam.lib ../libs/pythonXY.lib
第一個命令建立了三個檔案:
spam.obj
、spam.dll
和spam.lib
。Spam.dll
不包含任何 Python 函式(例如PyArg_ParseTuple()
),但由於pythonXY.lib
,它知道如何找到 Python 程式碼。第二個命令建立了
ni.dll
(以及.obj
和.lib
),它知道如何從 spam 以及 Python 可執行檔案中找到必要的函式。
並非每個識別符號都匯出到查詢表。如果您希望任何其他模組(包括 Python)能夠看到您的識別符號,您必須使用 _declspec(dllexport)
,例如 void _declspec(dllexport) initspam(void)
或 PyObject _declspec(dllexport) *NiGetSpamData(void)
。
Developer Studio 將會包含許多您實際上不需要的匯入庫,這會使您的可執行檔案增加約 100K。要去除它們,請使用“專案設定”對話方塊中的“連結”選項卡,指定 忽略預設庫。將正確的 msvcrtxx.lib
新增到庫列表中。