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. 實用方法¶
與 Unix 一樣,在 Windows 上構建擴充套件模組有兩種方法:使用 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 *
;它確實會建立一個單獨的副本。
5.3. 實際使用 DLL¶
Windows Python 是在 Microsoft Visual C++ 中構建的;使用其他編譯器可能會或可能不會起作用。本節的其餘部分是 MSVC++ 特有的。
在 Windows 中建立 DLL 時,您必須將 pythonXY.lib
傳遞給連結器。要構建兩個 DLL,spam 和 ni(使用在 spam 中找到的 C 函式),您可以使用以下命令
cl /LD /I/python/include spam.c ../libs/pythonXY.lib
cl /LD /I/python/include ni.c spam.lib ../libs/pythonXY.lib
第一個命令建立了三個檔案:spam.obj
、spam.dll
和 spam.lib
。Spam.dll
不包含任何 Python 函式(例如 PyArg_ParseTuple()
),但它知道如何找到 Python 程式碼,這要歸功於 pythonXY.lib
。
第二個命令建立了 ni.dll
(以及 .obj
和 .lib
),它知道如何從 spam 和 Python 可執行檔案中找到必要的函式。
並非每個識別符號都匯出到查詢表。如果您希望任何其他模組(包括 Python)能夠看到您的識別符號,您必須宣告 _declspec(dllexport)
,例如 void _declspec(dllexport) initspam(void)
或 PyObject _declspec(dllexport) *NiGetSpamData(void)
。
Developer Studio 將會拋入許多您實際上不需要的匯入庫,從而使您的可執行檔案增加大約 100K。要擺脫它們,請使用“專案設定”對話方塊中的“連結”選項卡來指定忽略預設庫。將正確的 msvcrtxx.lib
新增到庫列表中。