在Visual Studio中使用Debug Visualizers在C++中實現對原始類的自定義調試信息顯示


在Visual Studio中使用Debug Visualizers在C++中實現對原始類的自定義調試信息顯示

當我們在VS的C++中使用vector、list、map等這些STL容器,在開啟調試的時候可以看到這樣的信息:


然而在我們自己手寫鏈表,調試的時候卻要像這樣一級一級展開,很是麻煩。

有時候會想,如果要能像STL里面的list那樣子直接顯示出來就方便許多。經過幾番尋找,終於被我找到了方法。

使用 .natvis 文件

.natvis文件使用了xml格式來進行擴展,在%VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers路徑中,stl.nativs文件包含了C++中幾乎所有常用類的自定義調試信息,可以去翻閱里面的一些常用類來學習使用,原理也不是很復雜。
你可以自行編寫一個.natvis的文件,但是需要將該文件放到以下兩個路徑之一:
%VSINSTALLDIR%\Common7\Packages\Debugger\Visualizers(需求管理員權限)

%USERPROFILE%\My Documents\Visual Studio 2017\Visualizers(若不存在Visualizers文件夾可自行新建一個)
該文件的編寫有一個好處:你可以保持VS在調試狀態,然后實時去修改.natvis文件。當你保存的時候,就會立即作用於調試窗口。而如果編寫出現語法錯誤的話,則調試器會以原始的形式顯示(或者找到另一個可用的顯示)。
在你想要開始嘗試編寫該種格式的文件前,可以先在工具--選項--調試--輸出窗口--Natvis診斷信息(僅限C++)選擇為詳細,這樣在保存.natvis沒得到理想結果后在輸出窗口可以看到錯誤消息,不需要的時候再關掉即可。

新建一個 .natvis 文件

在項目中右鍵添加新建項,選擇Visual C++中的實用工具,找到調試器可視化文件,然后修改新建位置到上述兩個路徑之一。

新建好后,就可以看到它默認生成的代碼。

這篇博文並不打算從繁雜的語法開始講起,而是直接以各種實例來進行說明。而且在輸入這些代碼的時候會有代碼補全和功能提示,可以自己多動手嘗試。有興趣的話可以去參考文章末尾的鏈接。

自定義數組結構體/類

現在有一個簡易的數組結構體:

typedef struct Array
{
	int *data;
	int size;
} Array;

又或者是個類:

class Array
{
	//...
private:
	int *data;
	int size;
};

然后對應的.natvis格式文件如下:

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="Array">
    <DisplayString> {{ size = {size} }}</DisplayString>
    <Expand>
      <Item Name="[size]">size</Item>
      <ArrayItems>
        <Size>size</Size>
        <ValuePointer>data</ValuePointer>
      </ArrayItems>
    </Expand>
  </Type>
</AutoVisualizer>

最終的顯示效果如下:

當Array為class的時候上述文件也是有效的。

其中,Type Name指定了需要可視化的類型名。
DisplayString中的內容指定了該變量在這一列中需要顯示的內容,{{}}兩對大括號使得在調試器中輸出{},而{}單對大括號用於引用變量內的成員。
Expand用於指定變量展開時需要顯示的項,其中顯示的原始視圖對應未使用Debug Visualizers的情況。
Item可以指定需要添加可視化輸出的成員項,這里可以指定Name的字符串來決定在名稱這一列顯示什么,而中間的size則是指定了需要顯示的成員的值(這里不需要加任何別的修飾)。
ArrayItems說明需要顯示的數據類型是連續內存的數組,在內部的Size指定了需要顯示的數目,這里綁定到成員size,而ValuePointer則需要綁定數組首元素的指針。

當你需要將一維數組當多維數組來使用,或者使用定容的多維數組(如int[2][3])時,可以添加指定數組的秩信息。以一維擴展成二維為例:

struct Matrix
{
	//...
	int *mat;			// 矩陣
	int dimen[2];	// 兩個維度對應的大小
};

對應的.natvis文件格式如下:

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="Matrix">
    <DisplayString> {{ row = {dimen[0]}, column = {dimen[1]} }}</DisplayString>
    <Expand>
      <Item Name="[row]">dimen[0]</Item>
      <Item Name="[col]">dimen[1]</Item>
      <ArrayItems>
        <Direction>Forward</Direction>
        <Rank>2</Rank>
        <Size>dimen[$i]</Size>
        <ValuePointer>mat</ValuePointer>
      </ArrayItems>
    </Expand>
  </Type>
</AutoVisualizer>

最終調試窗口效果如下:

其中,Direction決定了如何展開多維數組的索引,Forward采用行優先展開,Backward采用列優先展開。
Rank指定了矩陣的維度。
Size中使用了$i作為循環遍歷用的索引,需要在原結構體中有一個數組存儲每一維度下的大小,然后在Size中指定該數組。
ValuePointer中如果指向的數組不是一維的,則需要在.natvis文件中寫成 (T*)data的形式。

自定義非連續內存的數組結構體/類

該節適用於那些使用指針數組、多級指針的多維數組,特點都是數組在內存上是非連續的。但使用該項的缺點是僅可以一維展開顯示,不能像上面那樣多維顯示。參考下面的類:

template<class T>
class Table
{
	//...
private:
	T** data;
	int col;
	int row;
};

對應的.natvis文件格式如下(對於字符串中的左、右尖括號請用對應的轉義字符替代):

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="Table&lt;*&gt;">
    <DisplayString> {{ row = {row}, column = {col} }}</DisplayString>
    <Expand>
      <Item Name="[row]">row</Item>
      <Item Name="[col]">col</Item>
      <IndexListItems>
        <Size>row * col</Size>
        <ValueNode>data[$i / col][$i % col]</ValueNode>
      </IndexListItems>
    </Expand>
  </Type>
</AutoVisualizer>

顯示效果如下:

Type Name這里第一次用到了類模板,為了能夠讓我們的類型名能夠適配所有類型,需要用到通配符*來匹配任意數目的字符。

注意:Table<*>的左右尖括號是其XML對應的轉義字符,而不是直接輸入<>。

由於IndexListItems僅能指定SizeValueNode兩個類型,因此它所能做的事情還是非常有限的。
其中Size指定了元素總數目。
注意到ValueNode在這里一定要顯式指定$i以循環遍歷輸出的對應元素,這里采用的是行優先展開的形式來輸出的。

自定義鏈表

現有自定義的鏈表結構體如下:

struct ListNode
{
	//...
	int val;
	ListNode *next;
};

struct List
{
	//...
	ListNode* head;
	int size;
};

對應的.natvis文件格式如下:

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="List">
    <DisplayString> {{ size = {size} }}</DisplayString>
    <Expand>
      <Item Name="[size]">size</Item>
      <LinkedListItems Condition="size > 0">
        <Size>size</Size>
        <HeadPointer>head</HeadPointer>
        <NextPointer>next</NextPointer>
        <ValueNode>val</ValueNode>
      </LinkedListItems>
    </Expand>
  </Type>
</AutoVisualizer>

最終顯示的效果如下:

這里用到的類型為LinkedListItems,在后面還添加了Condition作為顯示的條件,字符串的內容即為需要判別的表達式。當表達式為true時,才會顯示該項內容。
Size指定了鏈表的元素數目,這樣調試器就會根據該項顯示指定數目的元素。若這里沒有指定Size,則調試器會自動對鏈表進行推導直到遇到空指針結束。通常指定大小的話可以提高調試程序的性能。
HeadPointer指定要用到的頭結點指針
NextPointer指定頭結點中的next指針
ValueNode指定結點中的值成員

自定義二叉排序樹

由於調試輸出是按照中序遍歷的形式進行的,一般來說常用在二叉排序樹上(如map和set等),當然也可以是帶指向parent的三叉樹。如果你想要直接用普通的二叉樹的話也是可以的。現在有如下結構體/類:

struct TreeNode
{
	//...
	TreeNode* leftChild;
	TreeNode* rightChild;
	int val;
};

class BSTree
{
	//...
private:
	TreeNode* root;
	int size;
};

對應的.natvis文件代碼如下:

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="BSTree">
    <DisplayString> {{ size = {size} }}</DisplayString>
    <Expand>
      <Item Name="[size]">size</Item>
      <TreeItems Condition="size > 0">
        <Size>size</Size>
        <HeadPointer>root</HeadPointer>
        <LeftPointer>leftChild</LeftPointer>
        <RightPointer>rightChild</RightPointer>
        <ValueNode>val</ValueNode>
      </TreeItems>
    </Expand>
  </Type>
</AutoVisualizer>

最終調試輸出效果如下:

這里用到的類型為TreeItems
Size指定了樹的元素數目,這樣調試器就會根據該項顯示指定數目的元素。若這里沒有指定Size,則調試器會自動對鏈表進行推導直到遇到空指針結束。通常指定Size的話可以提高調試程序的性能。
HeadPointer指定了樹的根結點指針
LeftPointer指定了結點的左孩子指針
RightPointer指定了結點的右孩子指針
ValueNode指定了要輸出的值

如果這里用到的是key和value的組合的話,只需要將<ValueNode>修改成<ValueNode Name = "[{key}]">的形式即可。

還有許多高級的功能可以嘗試自行摸索。

參考鏈接:
https://blogs.msdn.microsoft.com/vcblog/2015/09/28/debug-visualizers-in-visual-c-2015/
https://msdn.microsoft.com/zh-cn/library/jj620914(v=vs.110).aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM