zoukankan      html  css  js  c++  java
  • 在Visual Studio中使用Debug Visualizers在C++中实现对原始类的自定义调试信息显示

    在Visual Studio中使用Debug Visualizers在C++中实现对原始类的自定义调试信息显示

    当我们在VS的C++中使用vector、list、map等这些STL容器,在开启调试的时候可以看到这样的信息:


    然而在我们自己手写链表,调试的时候却要像这样一级一级展开,很是麻烦。

    有时候会想,如果要能像STL里面的list那样子直接显示出来就方便许多。经过几番寻找,终于被我找到了方法。

    使用 .natvis 文件

    .natvis文件使用了xml格式来进行扩展,在%VSINSTALLDIR%Common7PackagesDebuggerVisualizers路径中,stl.nativs文件包含了C++中几乎所有常用类的自定义调试信息,可以去翻阅里面的一些常用类来学习使用,原理也不是很复杂。
    你可以自行编写一个.natvis的文件,但是需要将该文件放到以下两个路径之一:
    %VSINSTALLDIR%Common7PackagesDebuggerVisualizers(需求管理员权限)

    %USERPROFILE%My DocumentsVisual Studio 2017Visualizers(若不存在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

  • 相关阅读:
    scrapy+pymongo爬取小说实战
    Scrapy的正确安装
    linux: 用户管理,文件传送
    Java日期时间处理总结
    Numpy快速入门
    python 文件与文件夹操作
    python文件基础
    26. 删除排序数组中的重复项
    1两数之和
    152乘积最大子数组
  • 原文地址:https://www.cnblogs.com/X-Jun/p/8040916.html
Copyright © 2011-2022 走看看