在 Visual Studio 中一起偵錯 Python 和 C++

大多數常規的 Python 偵錯程式僅支援偵錯 Python 程式碼,但開發者通常使用 Python 搭配 C 或 C++。 一些使用混合程式碼的情況是需要高效能或能夠直接呼叫平台 API 的應用程式通常使用 Python 和 C 或 C++ 進行編碼。

Visual Studio 為 Python 和原生 C/C++ 程式碼提供整合的同時混合模式偵錯。 當您在 Visual Studio 安裝程式中為 Python Development 工作負載選取 Python 原生開發工具,即可使用支援:

顯示 Visual Studio 安裝程式中選取的 [Python 原生開發工具] 選項的螢幕擷取畫面。

在本文中,您將探索如何使用下列混合模式偵錯功能:

  • 合併的呼叫堆疊
  • 在 Python 和機器碼之間逐步執行
  • 這兩種類型程式碼中的中斷點
  • 檢視原生框架中物件的 Python 表示,反之亦然
  • 在 Python 專案或 C++ 專案的內容中進行偵錯

顯示 Visual Studio 中 Python 和 C++ 程式碼混合模式偵錯範例的螢幕擷取畫面。

必要條件

  • Visual Studio 2017 及更新版本。 在 Visual Studio 2015 及更早版本中,Python Tools for Visual Studio 1.x 無法使用混合模式偵錯。

  • 已安裝 Visual Studio 並支援 Python 工作負載。 如需詳細資訊,請參閱在 Visual Studio 中安裝 Python 支援。

在 Python 專案中啟用混合模式偵錯

下列步驟說明如何在 Python 專案中啟用混合模式偵錯:

  1. Solution Explorer,以滑鼠右鍵按一下 Python 專案,然後選取 Properties。

  2. Properties 窗格中,選取 Debug 頁籤,然後選取 >DebugEnable native code debugging 選項:

    顯示如何在 Visual Studio 中設定 [啟用機器碼偵錯] 屬性的螢幕擷取畫面。

    這個選項會啟用所有偵錯工作階段的混合模式。

    提示

    當您啟用原生程式碼偵錯時, Python 輸出視窗可能會在程式完成後立即關閉,而不會暫停並顯示 Press any key to continue。 若要在啟用原生程式碼偵錯後強制暫停並提示,請將-i引數新增至 [Debug] 索引標籤上的 >[Run Interpreter Arguments] 欄位。這個引數會在程式碼執行後讓 Python 解譯器進入互動模式。 程式會等候您選取 +Crl Z +Enter,以關閉視窗。

  3. 選取 [檔案]>[儲存] (或 Ctrl+S) 以儲存屬性變更。

  4. 若要將混合模式偵錯工具連結至現有處理序,請選取[偵錯]>[連結至處理序]。 對話方塊開啟。

    1. 在 [連結至處理序]對話方塊中,從清單中選取適當的處理序。

    2. 在 [連結至] 欄位中,使用 [選取] 選項開啟 [選取程式碼類型] 對話方塊。

    3. Select Code Type 對話方塊中,選擇 Debug these code types 選項。

    4. 在清單中,選取 Python (native) 核取方塊,然後選取 OK:

      顯示如何在 Visual Studio 中選取用於偵錯的 Python (原生) 程式碼型別的螢幕擷取畫面。

    5. 選取 [連結] 以啟動偵錯工具。

    程式碼型別設定是永久性的。 如果您想要停用混合模式偵錯,並在稍後連結至不同的程序,請清除 Python (native) 程式碼類型核取方塊,然後選取 Native 程式碼類型核取方塊。

    您可以選取除 Native 選項以外的其他程式碼型別。 例如,如果受管理的應用程式裝載 CPython (且使用原生擴充模組),而您想要偵錯所有三個程式碼專案,請選取 Python、Native、和 Managed 核取方塊。 此方法為您提供統一的偵錯體驗,包括組合呼叫堆疊和跨所有三個執行時步進。

使用虛擬環境

當您針對虛擬環境 (venvs) 使用這種混合模式偵錯方法時,Python for Windows 會針對 venvs 使用 python.exe 檔案,Visual Studio 會以子程序的方式尋找並載入該檔案。

  • 對於 Python 3.8 和更高版本,混合模式不支援多程序偵錯。 當您啟動偵錯工作階段時,會偵錯存根子處理序,而不是應用程式。 對於連結方案,解決方法是連結至正確的 python.exe 檔案。 當您以偵錯方式啟動應用程式時 (例如透過 F5 鍵盤快速鍵),您可以使用命令 C:\Python310-64\python.exe -m venv venv --symlinks 來建立您的 venv。 在指令中,插入您偏好的 Python 版本。 依照預設,只有系統管理員可以在 Windows 上建立符號連結。

  • 對於 3.8 之前的 Python 版本,混合模式偵錯應該與 venvs 一起正常工作。

在全域環境中運行不會導致任何版本的 Python 出現這些問題。

安裝 Python 符號

第一次在混合模式下開始偵錯時,可能會看到 Python Symbols Required 對話方塊。 對於任何給定的 Python 環境,您只需要安裝一次符號。 如果您透過 Visual Studio 安裝程式 (Visual Studio 2017 及更新版本) 安裝 Python 支援,會自動包含元件。 有關詳細資訊,請參見在 Visual Studio 中安裝 Python 解譯器的偵錯符號。

存取 Python 原始碼

您可以在偵錯時提供標準 Python 本身的原始程式碼。

  1. 移至 https://www.python.org/downloads/source/

  2. 下載適合您版本的 Python 原始程式碼封存,並將程式碼解壓縮至資料夾。

  3. 當 Visual Studio 提示輸入 Python 原始程式碼的位置時,請指向解壓縮資料夾中的特定檔案。

在 C/C++ 專案中啟用混合模式偵錯

Visual Studio 2017 15.5 及更新版本支援從 C/C++ 專案進行混合模式偵錯。 這個用法的範例是當您想要將 Python 嵌入另一個應用程式時,如 python.org。

下列步驟說明如何為 C/C++ 專案啟用混合模式偵錯:

  1. Solution Explorer,以滑鼠右鍵按一下 C/C++ 專案,然後選取 Properties。

  2. Property Pages 窗格中,選取 Configuration >PropertiesDebugging 頁籤。

  3. 展開 Debugger to launch 選項的下拉式功能表,然後選取 Python/Native Debugging。

    顯示如何在 Visual Studio 中選取 C/C++ 專案的 [Python 原生偵錯] 選項的螢幕擷取畫面。

    注意

    如果您未看到 Python/Native Debugging 選項,您必須先使用 Visual Studio Installer 安裝 Python 原生開發工具。 在 Python 開發工作負載下,可以使用本機偵錯選項。 如需詳細資訊,請參閱在 Visual Studio 中安裝 Python 支援。

  4. 選取 OK,儲存變更。

偵錯程式啟動器

使用此方法時,無法偵錯 py.exe 程式啟動器,因為它派生子 python.exe 子程序。 偵錯工具未連結至子程序。 對於這種情形,解決方法是直接啟動帶引數的 python.exe 程式,如下所示:

  1. 在 C/C++ 專案的 Property Pages 窗格中,移至 Configuration >Properties Debugging 頁籤。

  2. 在 [命令] 選項中,指定 python.exe 程式檔案的完整路徑。

  3. Command Arguments 欄位中指定您想要的引數。

連結混合模式偵錯工具

對於 Visual Studio 2017 15.4 版及更早版本,只有在 Visual Studio 中啟動 Python 專案時,才能啟用直接混合模式偵錯。 由於 C/C++ 專案僅使用原生偵錯工具,因此支援受到限制。

對於此情況,解決方法是單獨連結偵錯工具:

  1. 選取 [偵錯]>[啟動但不偵錯],或使用鍵盤快速鍵 Ctrl+F5 在不偵錯的情況下啟動 C++ 專案。

  2. 若要將混合模式偵錯工具連結至現有處理序,請選取[偵錯]>[連結至處理序]。 對話方塊開啟。

    1. 在 [連結至處理序]對話方塊中,從清單中選取適當的處理序。

    2. 在 [連結至] 欄位中,使用 [選取] 選項開啟 [選取程式碼類型] 對話方塊。

    3. Select Code Type 對話方塊中,選擇 Debug these code types 選項。

    4. 在清單中,選取 Python,然後選取 OK。

    5. 選取 [連結] 以啟動偵錯工具。

提示

您可以在 C++ 應用程式中新增暫停或延遲,以確保它不會在連結偵錯工具之前呼叫您要偵錯的 Python 程式碼。

探索混合模式的特定功能

Visual Studio 提供多種混合模式偵錯功能,可讓您更容易偵錯您的應用程式:

使用組合呼叫堆疊

[呼叫堆疊] 視窗會穿插顯示原生和 Python 堆疊框架,而兩者間會標示轉換:

Visual Studio 中具有混合模式偵錯的合併呼叫堆疊視窗的螢幕擷取畫面。

  • 若要使轉接顯示為 [External Code],而不指定轉接>方向,請設定 Tools >Options Debugging >General Enable >Just My Code 選項。

  • 若要啟動任何呼叫框架,請連按兩下框架。 如果可能,此操作還會開啟相應的原始碼。 如果原始程式碼無法使用,框架仍會變成作用中,而且可以檢查區域性變數。

在 Python 和機器碼之間逐步執行

Visual Studio 提供 Step Into (F11) 或 Step Out (Shift F11) 命令,以使混合模式+偵錯工具能夠正確處理程式碼型別之間的變更。

  • 當 Python 呼叫一個在 C 中實現的型別的方法時,對該方法的呼叫在原生函式開始執行時停止。

  • 當原生程式碼呼叫 Python API 函式,導致呼叫 Python 程式碼時,也會發生同樣的行為。 對最初在 Python 中定義的函式值執行呼叫 PyObject_CallObject 的步驟會在 Python 函式的開頭停止。

  • 對於透過 ctypes 從 Python 叫用的原生函式,也支援從 Python 逐步執行到原生函式。

在原生程式碼中使用 PyObject 值檢視

當原生 (C 或 C++) 框架為使用中時,其區域變數會顯示在偵錯工具的 [區域變數] 視窗。 在原生 Python 擴充模組中,這些變數中有許多是型別 PyObject(這是 typedef),_object或者一些其他基本 Python 型別。 在混合模式偵錯中,這些值會顯示另一個標示為 [Python 檢視] 的子節點。

  • 若要檢視變數的 Python 表示,請展開節點。 變數的檢視與您看到的在 Python 框架中是否存在參照相同物件的區域性變數完全相同。 此節點的子系是可編輯的。

    顯示 Visual Studio [區域變數] 視窗中 [Python 檢視] 的螢幕擷取畫面。

  • 若要停用這項功能,請以滑鼠右鍵按一下 [區域變數] 視窗的任意處,然後切換 [Python]>[顯示 Python 檢視節點] 功能表選項:

    顯示如何啟用 [區域變數] 視窗的 [顯示 Python 檢視節點] 選項的螢幕擷取畫面。

顯示 Python 檢視節點的 C 型別

下列 C 型別會顯示 [Python 檢視] (如果啟用):

  • PyObject
  • PyVarObject
  • PyTypeObject
  • PyByteArrayObject
  • PyBytesObject
  • PyTupleObject
  • PyListObject
  • PyDictObject
  • PySetObject
  • PyIntObject
  • PyLongObject
  • PyFloatObject
  • PyStringObject
  • PyUnicodeObject

[Python 檢視],不會針對您自己撰寫的型別自動顯示。 當您編寫 Python 3.x 的擴充功能時,這種缺缺通常不是問題。 任何物件最終都有一個列出的 ob_baseC 型別之一的欄位,這會導致 [Python 檢視]。

在 Python 程式碼中檢視原生值

當 Python 框架處於作用中狀態時,您可以在 Locals 視窗中啟用原生值的 [C++ 檢視]。 此功能預設不會啟用。

  • 若要啟用此功能,請在 Locals 視窗中按一下滑鼠右鍵,並設定 >Python Show C++ View Nodes 功能表選項。

    顯示如何啟用 [區域變數] 視窗的 [顯示 C++ 檢視節點] 選項的螢幕擷取畫面。

  • [C++ view] 節點提供值的基礎 C/C++ 結構的表示,與您在原生框架中看到的相同。 它顯示 Python _longobject長整數的實例 (PyLongObject為 typedef),並嘗試推斷您自己編寫的原生類別的型別。 此節點的子系是可編輯的。

    顯示 Visual Studio [區域變數] 視窗中的 C++ 檢視的螢幕擷取畫面。

如果物件的子欄位為型別PyObject或其他支援的型別,則它具有 [Python 檢視] 表示節點 (如果這些表示已啟用)。 這個行為使得瀏覽對象圖形成為可能,其中連結不會直接暴露給 Python。

不同於 [Python 檢視] 節點使用 Python 物件中繼資料來判斷物件的類型,[C++ 檢視] 沒有類似的可靠機制。 一般而言,憑藉一個 Python 值 (也就是 PyObject 參考) 是無法可靠地判斷支援它的 C/C++ 結構。 混合模式偵錯工具會嘗試PyTypeObject透過檢視具有函式指標型別的物件型別的各個欄位 ob_type(例如其欄位所參考的欄位) 來猜測型別。 如果其中一個函式指標引用可解析的函式,並且該函式的引數self型別位元定型別更多,PyObject*則將該型別假定為支援型別。

考慮下列範例,其中指定物件的值ob_type->tp_init指向下列函式:

static int FobObject_init(FobObject* self, PyObject* args, PyObject* kwds) {
    return 0;
}

在這種情況下,偵錯工具可以FobObject正確地推斷出對象的 C 型別。 如果偵錯工具無法從中判斷更精確的型別,tp_init則會移至其他欄位。 如果無法從這些欄位的任一個推論出類型,[C++ 檢視] 節點會將物件顯示為 PyObject 執行個體。

為了始終能夠取得自訂撰寫類型最有用的表示法,註冊類型時最好註冊至少一個特殊函式,並使用強型別 self 參數。 大多數型別自然地滿足這一要求。 對於其它型別,tp_init檢查通常是用於此目的的最方便入口。 對於僅用於啟用偵錯工具型別推斷的型別而tp_init呈現的虛擬實作,可以立即傳回零,如上述範例所示。

檢閱與標準 Python 偵錯的差異

混合模式偵錯工具與標準 Python 偵錯工具不同。 它引入一些額外功能,但缺少一些與 Python 相關的功能,如下所示:

  • 不支援的功能包括條件中斷點、Debug Interactive 視窗以及跨平台遠端偵錯。
  • 可以使用 Immediate 視窗,但其功能只包含有限的子集,其中包括本節中列出的所有限制。
  • 支援的 Python 版本只包括 CPython 2.7 和 3.3+。
  • 若要將 Python 與 Visual Studio Shell 搭配使用 (例如,如果您使用整合式安裝程式安裝它),Visual Studio 無法開啟 C++ 專案。 因此,C++ 檔案的編輯體驗僅限於基本的文字編輯器。 不過,Shell 搭配偵錯工具視窗中的原始程式碼、逐步執行到原生程式碼及 C++ 運算式評估,可完整支援 C/C++ 偵錯和混合模式偵錯。
  • 當您在 Locals 和 Watch 偵錯工具視窗中檢視 Python 物件時,混合模式偵錯工具只會顯示物件的結構。 它不會自動評估屬性或顯示計算的屬性。 對於集合,它只會顯示內建集合類型的元素 (tuplelistdictset)。 自訂集合型別不會視覺化為集合,除非它們是從某些內建集合型別繼承而來。
  • 表達式求值將按下一節中的說明進行處理。

使用運算式評估

標準的 Python 偵錯程式允許當偵錯進程在程式碼中的任何點被暫停時,在 Watch 和 Immediate 視窗中計算任意 Python 表達式,只要它在 I/O 操作或其他類似的系統呼叫中未被阻止。 在混合模式偵錯,任意的運算式只能在 Python 程式碼中停止時、中斷點之後,或逐步執行程式碼時進行評估。 只能在發生中斷點或逐步執行作業的執行緒上評估運算式。

當偵錯程式在原生程式碼中停止,或在所描述條件不適用的 Python 程式碼中停止 (例如,在步出作業之後,或在不同的執行緒上)。 運算式評估僅限於存取目前選取之框架範圍內的本機及全域變數、存取其欄位,以及用文字索引內建集合型別。 例如,下列運算式可以在任何前後關聯中計算 (假設所有識別碼都參照適當的型別之現有變數與欄位):

foo.bar[0].baz['key']

混合模式偵錯工具也會以不同方式解析這類運算式。 所有成員存取作業只會查詢直接屬於物件的欄位 __dict__(例如其或中的專案或__slots__透過 Python 公開的原生結構tp_members欄位),並__getattr__忽略任何或__getattribute__描述元邏輯。 同樣地,所有編制索引作業會忽略 __getitem__,而直接存取集合的內部資料結構。

為了一致性,這個名稱解析方案會用於所有符合限制運算式評估之限制的運算式。 不論目前停止點是否允許任意運算式,都會套用此配置。 若要在有功能完整的評估工具時強制適當的 Python 語意,請將運算式括在括號中︰

(foo.bar[0].baz['key'])