在 Visual Studio 調試器中創建數據的自定義視圖C++/C#
調試器提供了許多用於檢查和修改程序狀態的工具。 這些工具中的大多數僅在中斷模式下有效。
在變量窗口和數據提示中創建數據的自定義視圖
許多調試器窗口(如 "自動" 和 "監視" 窗口)都允許您檢查變量。 您可以自定義C++類型、托管對象以及您自己的類型在調試器變量窗口和數據提示中的顯示方式。
創建自定義可視化工具
可視化工具使您能夠以有意義的方式查看對象或變量的內容。 在 Visual Studio 調試器中,可視化工具是指可以使用放大鏡
圖標打開的其他窗口。 例如,HTML 可視化工具顯示 HTML 字符串如何在瀏覽器中進行解釋和顯示。 你可以通過數據提示、"監視" 窗口 、"自動" 窗口和 "局部變量" 窗口訪問可視化工具。 "快速監視" 對話框還提供可視化工具。
自定義C++視圖
使用 Natvis 框架在C++調試器中創建對象的自定義視圖
Visual Studio Natvis 框架可以自定義本機類型在調試器變量窗口(例如局部變量、監視以及數據提示窗口)中顯示的方式。 Natvis 的可視化功能可以讓你創建的類型在調試期間更加直觀清晰。
Natvis 替換了 Visual Studio 早期版本中的 autoexp.dat 文件,提供了 XML 語法、更好的診斷功能、版本控制功能以及多文件支持功能。
Natvis 可視化效果
你可以使用 Natvis 框架為自己創建的類型創建可視化規則,讓開發人員在調試過程中更輕松地查看這些類型。
例如,下圖顯示的類型 Windows::UI::Xaml::Controls::TextBox 的變量在調試器窗口中未應用任何自定義可視化。

突出顯示的行顯示 Text 類的 TextBox 屬性。 由於類的層次結構很復雜,因此很難找到這個屬性。 調試器不知道如何解釋自定義字符串類型,所以你看不到文本框中的字符串。
如果應用了 Natvis 自定義可視化工具規則,那么在變量窗口中,同樣的TextBox看起來就簡單得多。 類的重要成員會顯示在一起,並且調試器會顯示自定義字符串類型的基礎字符串值。

在 C++ 項目中使用 .natvis 文件
Natvis 使用Natvis文件來指定可視化規則。 Natvis文件是具有NATVIS擴展名的 XML 文件。 Natvis 架構在 %VSINSTALLDIR%\Xml\Schemas\natvis.xsd中定義。
.natvis 文件的基本結構由一個或多個代表可視化條目的 Type 元素構成。 每個 Type 元素的完全限定名稱都在其 Name 屬性中指定。
<?xml version="1.0" encoding="utf-8"?> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="MyNamespace::CFoo"> . . </Type> <Type Name="..."> . . </Type> </AutoVisualizer>
Visual Studio 在 %VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers文件夾中提供了一些natvis文件。 這些文件包含許多通用類型的可視化規則,並且可用作編寫新類型的可視化效果的示例。
將 natvis 文件添加到C++項目
可以將natvis文件添加到任何C++項目。
若要添加新的natvis文件:
-
在解決方案資源管理器C++中選擇項目節點,然后選擇 "項目 > "添加新項",或右鍵單擊該項目並選擇"添加 > 新項"。

-
在 "添加新項" 對話框中,選擇 " Visual C++ > 實用工具" > 調試器可視化文件(. natvis) 。

-
命名該文件,然后選擇 "添加"。
新文件將添加到解決方案資源管理器,並在 Visual Studio 文檔窗格中打開。
Visual Studio 調試器會自動在 C++ 項目中加載 .natvis 文件,默認情況下,還會在生成項目時,在 .pdb 文件中包含它們。 在調試生成的應用時,調試器會從 .pdb 文件加載 .natvis 文件,即使你沒有打開該項目也是如此。 如果不想在 .pdb 中包含 .natvis 文件,可以從生成的 .pdb 文件中將其排除。
從 .pdb 中排除 .natvis 文件:
-
在解決方案資源管理器中選擇 .natvis 文件,然后選擇屬性圖標,或右鍵單擊該文件並選擇屬性。

-
按下從生成中排除旁邊的箭頭並選擇是,然后選擇確定。

如果是調試可執行的項目,則使用解決方案項來添加 .pdb 中不包含的 .natvis 文件,因為沒有可用的 C++ 項目。從 .pdb 加載的 Natvis 規則僅適用於 .pdb 引用的模塊中的類型。 例如,如果 Module1.pdb 包含一個名為 Test 的類型的 Natvis 條目,則它僅適用於 TestModule1.dll中的 類。 如果另一個模塊也定義了一個名為 Test 的類,則 Module1.pdb 的 Natvis 條目不適用於它。
通過 VSIX 包安裝並注冊natvis文件:
VSIX 包可以安裝和注冊natvis文件。 無論他們安裝在何處,都將在調試過程中自動提取所有注冊的natvis文件。
-
將natvis文件包括在 VSIX 包中。 例如,對於以下項目文件:
XML
-
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0"> <ItemGroup> <VSIXSourceItem Include="Visualizer.natvis" /> </ItemGroup> </Project> -
在source.extension.vsixmanifest文件中注冊natvis文件:
XML
-
<?xml version="1.0" encoding="utf-8"?> <PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> <Assets> <Asset Type="NativeVisualizer" Path="Visualizer.natvis" /> </Assets> </PackageManifest>
Natvis 文件位置
如果你希望將 .natvis 文件應用於多個項目,可以將它們添加到用戶目錄或系統目錄中。
將按照以下順序來評估 .Natvis 文件:
-
所調試的 .pdb 文件中內嵌的所有的 .natvis 文件,除非加載的項目中存在同名文件。
-
加載的 C++ 項目或頂級解決方案中的所有 .natvis 文件。 這包括所有已加載的 C++ 項目(包括類庫),但不包括其他語言的項目。
-
通過 VSIX 包安裝並注冊的任何natvis文件。
- 特定於用戶的 Natvis 目錄 (例如, %USERPROFILE%\Documents\Visual Studio 2019\Visualizers)。
- 系統級 Natvis 目錄 ( %VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers)。 此目錄中包含隨 Visual Studio 一起安裝的 .natvis 文件。 如果你具有管理員權限,可以將文件添加到此目錄中。
調試時修改 natvis 文件
你可以在調試項目時,在 IDE 中修改 .natvis 文件。 在用於調試的同一個 Visual Studio 實例中打開該文件,進行修改,然后保存。 保存后,監視和局部變量窗口就會隨之更新,顯示所做的更改。你還可以在調試的解決方案中添加或刪除 .natvis 文件,Visual Studio 會隨之添加或刪除相關的可視化效果。在調試時,無法更新 .pdb 文件中內嵌的 .natvis 文件。
如果你在 Visual Studio 以外修改 .natvis 文件,所做的更改不會自動生效。 若要更新調試器窗口,可以在監視窗口中重新計算 .natvisreload 命令。 更改隨后就會生效,無需重新啟動調試會話。
還可以使用 .natvisreload 命令將 .natvis 文件升級到較新的版本。 例如, .natvis 文件可能被納入了源代碼管理中,並且你需要獲取其他人最近所做的更改。
表達式和格式化
Natvis 的可視化功能使用 C++ 表達式來指定要顯示的數據項。 除了上下文運算符 (C++) 中所述的、調試器中的 C++ 表達式的增強功能和限制外,還要注意以下事項:
-
Natvis 表達式在可視化對象上下文而非當前堆棧框架中進行計算。 例如,Natvis 表達式中的
x指的是可視化對象中名為x的字段,而不是當前函數中名為x的局部變量。 盡管可以訪問全局變量,但不能訪問 Natvis 表達式中的局部變量。 -
Natvis 表達式不允許函數計算或副作用。 函數調用和賦值運算符會被忽略。 由於調試器內部函數沒有副作用,因此可以從任何 Natvis 表達式隨意調用,即使系統不允許進行其他函數調用也是如此。
-
要控制表達式的顯示方式,可以使用 C++ 中的格式說明符中所述的任何格式說明符。 如果條目由 Natvis 在內部使用,格式說明符將被忽略,例如
SizeArrayItems 擴展中的 表達式。
Natvis 視圖
你可以定義不同的 Natvis 視圖,以便用不同的方式來顯示類型。 例如,下面是 std::vector 的一個可視化效果,定義了一個名為 simple 的簡化視圖。 默認視圖和 DisplayString 視圖中都顯示了 ArrayItems 和 simple 元素,但 [size] 視圖中卻未顯示 [capacity] 和 simple 項。
<Type Name="std::vector<*>"> <DisplayString>{{ size={_Mylast - _Myfirst} }}</DisplayString> <Expand> <Item Name="[size]" ExcludeView="simple">_Mylast - _Myfirst</Item> <Item Name="[capacity]" ExcludeView="simple">_Myend - _Myfirst</Item> <ArrayItems> <Size>_Mylast - _Myfirst</Size> <ValuePointer>_Myfirst</ValuePointer> </ArrayItems> </Expand> </Type>
在監視窗口中,使用 ,view 格式說明符來指定替代視圖。 simple 視圖將顯示為 vec,view(simple) :

Natvis 錯誤
當調試器遇到可視化條目中的錯誤時,會忽略它們。 要么以原始格式顯示類型,要么選擇其他合適的可視化效果。 你可以使用 Natvis 的診斷功能來了解調試器忽略可視化條目的原因,還可以查看基礎語法和分析錯誤。
若要啟用 Natvis 診斷:
- 在 "工具" > 選項"(或"調試 > 選項") >調試 > 輸出窗口中,將" Natvis 診斷消息(C++僅) "設置為"錯誤"、"警告"或 " 詳細"

錯誤將顯示在 "輸出" 窗口中。
Visual C/C++自定義可視化工具兼容性
從 Visual Studio 2019 開始, C++包含一個改進的調試器,它使用外部64位進程來承載其內存密集型組件。 作為此更新的一部分,必須更新 C/C++ expression 計算器的某些擴展以使它們與新調試器兼容。
如果你當前使用的是舊的 CC++ /EE 外接程序C++或 c/自定義可視化工具,則可以通過轉到 "工具" > 選項" > 調試",然后取消選擇 "加載調試" 來關閉此外部進程的使用情況。外部進程中的符號(僅限本機) 。 如果取消選擇此選項,將會大幅增加 IDE (devenv)進程中的內存使用率。 因此,如果您希望調試大型項目,則建議您與擴展的所有者合作,使其與此調試選項兼容。
托管代碼自定義視圖
創建托管對象的自定義視圖
可以在調試器變量窗口中自定義 Visual Studio 顯示數據類型的方式。在C#、Visual Basic、 F#和C++ (C++僅限/cli 代碼)中,你可以使用 DebuggerTypeProxyAttribute、DebuggerDisplayAttribute 和 DebuggerBrowsableAttribute 為自定義數據添加擴展。
在 .NET Framework 2.0 代碼中,Visual Basic 不支持 DebuggerBrowsable 屬性。 此限制已在最新版本的 .NET 中刪除。
使用 DebuggerDisplay 特性(C#、Visual Basic、 F#、 C++/cli)告訴調試器要顯示的內容
DebuggerDisplayAttribute 控制對象、屬性或字段在調試器變量窗口中的顯示方式。 此特性可應用於類型、委托、屬性、字段和程序集。 如果應用於基類型,則屬性也適用於子類。DebuggerDisplay 特性有一個參數,此參數是要在值列中為類型的實例顯示的字符串。 此字符串可以包含大括號({ 和 })。 一對大括號之間的文本將作為字段、屬性或方法進行計算。
如果一個類中有重寫的 ToString() 方法,調試器將使用該重寫的方法而非默認 {<typeName>}。 因此,如果你已重寫 ToString() 方法,調試器將使用重寫的方法而非默認{<typeName>},你無需使用 DebuggerDisplay。 如果同時使用,DebuggerDisplay 屬性優先於替代的 ToString() 方法。 @No__t_0 特性還優先於子類中重寫的 ToString() 方法。
調試器是否計算此隱式 ToString() 調用取決於“工具”/“選項”/“調試” 對話框中的用戶設置。 Visual Basic 不實現此隱式 ToString() 計算。
如果在“工具”/“選項”/“調試” 對話框中選中了“在變量窗口中顯示對象的原始結構” 復選框,則將忽略 DebuggerDisplay 特性。對於本機代碼,此屬性僅在/Cli 代碼C++中受支持。
下表顯示 DebuggerDisplay 特性的一些可能用法和示例輸出。
| 特性 | 顯示在“值”列中的輸出 |
|---|---|
[DebuggerDisplay("x = {x} y = {y}")]在具有 x 和 y字段的類型上使用。 |
x = 5 y = 18 |
[DebuggerDisplay("String value is {getString()}")]參數語法在不同的語言中會有所不同。 因此,使用時要小心。 |
String value is [5, 6, 6] |
DebuggerDisplay 還可以接受命名參數。
| 參數 | 目標 |
|---|---|
Name,Type |
這些參數影響變量窗口的 “名稱” 和 “類型” 列。 (可將它們設置為使用與構造函數相同的語法的字符串。)如果過度使用這些參數或使用這些參數不當,則會導致混亂的輸出。 |
Target,TargetTypeName |
指定在程序集級別使用該特性時的目標類型。 |
autoexp.cs 文件在程序集級別使用 DebuggerDisplay 特性。 autoexp.cs 文件確定 Visual Studio 用於 .NET 對象的默認擴展。 可以檢查 autoexp.cs 文件以獲得如何使用 DebuggerDisplay 特性的示例,也可以修改和編譯 autoexp.cs 文件以更改默認擴展。 在修改 autoexp.cs 文件之前,一定要對該文件進行備份。
若要生成 autoexp.cs,請打開 VS2015 開發人員命令提示,並運行以下命令
cd <directory containing autoexp.cs> csc /t:library autoexp.cs
將在下一調試會話中選取對 autoexp.dll 的更改。
在 DebuggerDisplay 中使用表達式
雖然您可以在 DebuggerDisplay 特性中的大括號之間使用常規表達式,但建議不要這樣做。
DebuggerDisplay 中的常規表達式只能隱式訪問目標類型的當前實例的 this 指針。 該表達式不能訪問別名、局部變量或指針。 如果表達式引用屬性,則不處理這些屬性上的特性。 例如,如果字段 [DebuggerDisplay("Object {count - 2}")] 是 8,則 C# 代碼 Object 6 將顯示 count。
在 DebuggerDisplay 中使用表達式可能導致以下問題:
-
計算表達式是調試器中最消耗資源的操作,並且表達式在每次顯示時都會被計算。 在單步執行代碼時,這可能會導致性能問題。 例如,當元素的數量很大時,一個用來在集合或列表中顯示值的復雜表達式可能會很慢。
-
表達式是通過當前堆棧幀的語言表達式計算器計算的,而不是通過表達式記錄語言的計算器計算的。 當語言不同時,這可能導致不可預知的結果。
-
計算表達式可更改應用程序的狀態。 例如,設置屬性值的表達式在執行代碼時會改變屬性值。
減少表達式計算可能出現的問題的一種方法是創建執行操作並返回字符串的私有屬性。 然后,DebuggerDisplay 特性可以顯示該私有屬性的值。 以下示例實現了這種模式:
[DebuggerDisplay("{DebuggerDisplay,nq}")] public sealed class MyClass { public int count { get; set; } public bool flag { get; set; } private string DebuggerDisplay { get { return string.Format("Object {0}", count - 2); } } }
",Nq" 后綴通知表達式計算器在顯示最終值時刪除引號(nq = no 引號)。
示例
下面的代碼示例演示如何使用 DebuggerDisplay以及 DebuggerBrowseable 和 DebuggerTypeProxy。 在調試器變量窗口(如 “監視” 窗口)中查看時,它生成類似以下內容的擴展:
| 名稱 | “值” | Type |
|---|---|---|
| 項 | "three" | object {string} |
| “值” | 3 | object {int} |
[DebuggerDisplay("{value}", Name = "{key}")] internal class KeyValuePairs { private IDictionary dictionary; private object key; private object value; public KeyValuePairs(IDictionary dictionary, object key, object value) { this.value = value; this.key = key; this.dictionary = dictionary; } public object Key { get { return key; } set { object tempValue = dictionary[key]; dictionary.Remove(key); key = value; dictionary.Add(key, tempValue); } } public object Value { get { return this.value; } set { this.value = value; dictionary[key] = this.value; } } } [DebuggerDisplay("{DebuggerDisplay,nq}")] [DebuggerTypeProxy(typeof(HashtableDebugView))] class MyHashtable { public Hashtable hashtable; public MyHashtable() { hashtable = new Hashtable(); } private string DebuggerDisplay { get { return "Count = " + hashtable.Count; } } private class HashtableDebugView { private MyHashtable myhashtable; public HashtableDebugView(MyHashtable myhashtable) { this.myhashtable = myhashtable; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public KeyValuePairs[] Keys { get { KeyValuePairs[] keys = new KeyValuePairs[myhashtable.hashtable.Count]; int i = 0; foreach (object key in myhashtable.hashtable.Keys) { keys[i] = new KeyValuePairs(myhashtable.hashtable, key, myhashtable.hashtable[key]); i++; } return keys; } } } }
使用 DebuggerTypeProxy 特性(C#Visual Basic, C++/cli)告訴調試器要顯示的類型
DebuggerTypeProxyAttribute 指定類型的代理或替身,並更改類型在調試器窗口中的顯示方式。 查看具有代理的變量時,代理將代替“顯示”中的原始類型。 調試器變量窗口僅顯示代理類型的公共成員。 不會顯示私有成員。
此特性可應用於:
- 結構
- 類
- 程序集
備注
對於本機代碼,此屬性僅在/Cli 代碼C++中受支持。
類型代理類必須具有一個構造函數,該函數采用代理將替換的類型的參數。 在每次需要顯示目標類型的變量時,調試器都會創建類型代理類的一個新實例。 這會對性能產生一定影響。 因此,不應在構造函數中執行非必需的工作。
若要最大程度地減小性能損失,表達式計算器將不檢查類型的顯示代理上的特性,除非用戶在調試器窗口中單擊 + 符號或使用 DebuggerBrowsableAttribute 擴展該類型。 因此,不應將特性置於顯示類型自身中。 特性可以且應該用於顯示類型的正文中。
類型代理最好是作為特性目標類中的私有嵌套類。 這樣,它便能輕松訪問內部成員。
可以繼承 DebuggerTypeProxyAttribute,因此,如果在基類上指定了類型代理,則它將應用於任何派生類,除非這些派生類指定其自己的類型代理。
如果在程序集級別使用 DebuggerTypeProxyAttribute,則 Target 參數將指定代理要替換的類型。
有關如何將此屬性與 DebuggerDisplayAttribute 和 DebuggerTypeProxyAttribute 一起使用的示例,請參閱使用 DebuggerDisplay 屬性。
將泛型與 DebuggerTypeProxy 一起使用
對泛型的支持是有限的。 對於 C#,DebuggerTypeProxy 只支持開放類型。 開放類型(也稱作“非構造類型”)是一種還未使用其類型參數的自變量實例化的泛型類型。 不支持封閉類型(也稱作“構造類型”)。
開放類型的語法類似於:
Namespace.TypeName<,>
如果使用泛型類型作為 DebuggerTypeProxy 中的目標,則必須使用該語法。 DebuggerTypeProxy 機制將為你推理類型參數。
有關中C#的 C# 打開和關閉類型的詳細信息,請參閱語言規范部分20.5.2 打開和關閉類型。
Visual Basic 沒有開放類型語法,因此您無法在 Visual Basic 中執行同樣的操作。 而必須使用開放類型名稱的字符串表示形式。
"Namespace.TypeName'2"
