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.soC.so 的連結器;這會導致它被包含兩次,因此 B 和 C 將各自擁有自己的副本。在 Windows 中,構建 A.dll 也會構建 A.lib。您 確實A.lib 傳遞給 B 和 C 的連結器。A.lib 不包含程式碼;它只包含將在執行時用於訪問 A 程式碼的資訊。

在 Windows 中,使用匯入庫有點像使用 import spam;它讓您可以訪問 spam 的名稱,但不會建立單獨的副本。在 Unix 中,與庫連結更像是 from spam import *;它確實會建立單獨的副本。

關閉 CPython 標頭檔案中執行的與 Python 庫的隱式、基於 #pragma 的連結。

在 3.14 版本加入。

5.3. DLLs 的實際使用

Windows Python 是用 Microsoft Visual C++ 構建的;使用其他編譯器可能有效也可能無效。本節的其餘部分是 MSVC++ 特有的。

在 Windows 中建立 DLL 時,您可以透過兩種方式使用 CPython 庫:

  1. 預設情況下,直接或透過 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.objspam.dllspam.libSpam.dll 不包含任何 Python 函式(例如 PyArg_ParseTuple()),但由於隱式連結的 pythonXY.lib,它知道如何找到 Python 程式碼。

    第二個命令建立了 ni.dll(以及 .obj.lib),它知道如何從 spam 以及 Python 可執行檔案中找到必要的函式。

  2. 透過在包含 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.objspam.dllspam.libSpam.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 新增到庫列表中。