在VisualStudio2012中,我们引入了使用 纳维斯 文件夹。 VisualStudio2013包含一些改进,这些改进使得为内部使用集合来存储项的类编写可视化更容易。 在这篇博文中,我将展示一个我们想要改进的示例场景,向您展示在VS2012中必须做些什么才能达到预期的效果,然后通过探索我们的一些新增强,向您展示natvis创作如何在VS2013中变得更容易。
让我们考虑以下源代码,并假设我们对为CNameList类编写可视化工具很感兴趣:
#include <vector>using namespace std;class CName{private: string m_first; string m_last;public: CName(string first, string last) : m_first(first), m_last(last) {} void Print() { wprintf(L"%s %s", (const char*) m_first.c_str(), (const char*) m_last.c_str()); }};class CNameList{private: vector m_list;public: CNameList() {} ~CNameList() { for (int i = 0; i < m_list.size(); i++) { delete m_list[i]; } m_list.clear(); } void AddName(string first, string last) { m_list.push_back(new CName(first, last)); }};int _tmain(int argc, _TCHAR* argv[]){ CNameList presidents; presidents.AddName("George", "Washington"); presidents.AddName("John", "Adams"); presidents.AddName("Thomas", "Jefferson"); presidents.AddName("Abraham", "Lincoln"); return 0;}
我们的目标是让“总统”像这样展示:
在visualstudio2012中,为CNameList类编写可视化效果对某些人来说可能很棘手。 最明显的纳特维斯创作:
<?xml version="1.0" encoding="utf-8"?><AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="CNameList"> <Expand> <ExpandedItem>m_list</ExpandedItem> </Expand> </Type> <Type Name="CName"> <DisplayString>{m_first} {m_last}</DisplayString> <Expand> <Item Name="First">m_first</Item> <Item Name="Last">m_last</Item> </Expand> </Type></AutoVisualizer>
将显示如下:
虽然总统的名字和姓氏都在那里,但景色比我们想象的要杂乱得多。 因为我们想要可视化CNameList对象的内容,而不是它的实现细节,所以我们可能不关心内部向量的大小或容量,也不关心列表中每个CName对象的内存地址,也不关心名字和姓氏周围的引号,以指示它们被存储为单独的字符串。 使用visualstudio2012,消除这种混乱是可能的,但是它相当麻烦,并且需要将CName和CNameList的可视化与STL的实现细节结合起来。 例如,在VS2012中,我们可以通过将CNameList的visualizer替换为:
<Type Name="CNameList"> <Expand> <IndexListItems> <Size>m_list._Mylast - m_list._Myfirst</Size> <ValueNode>*m_list._Myfirst[$i]</ValueNode> </IndexListItems> </Expand> </Type>
我们可以用这个替换CName可视化工具,去掉名字和姓氏之间的引号,它使用“sb” 格式限定符 要删除字符串中字符周围的引号,请执行以下操作:
<Type Name="CName"> <DisplayString Condition="m_first._Myres < m_first._BUF_SIZE && m_last._Myres < m_last._BUF_SIZE">{m_first._Bx._Buf,sb} {m_last._Bx._Buf,sb}</DisplayString> <DisplayString Condition="m_first._Myres >= m_first._BUF_SIZE && m_last._Myres < m_last._BUF_SIZE">{m_first._Bx._Ptr,sb} {m_last._Bx._Buf,sb}</DisplayString> <DisplayString Condition="m_first._Myres < m_first._BUF_SIZE && m_last._Myres >= m_last._BUF_SIZE">{m_first._Bx._Buf,sb} {m_last._Bx._Ptr,sb}</DisplayString> <DisplayString Condition="m_first._Myres >= m_first._BUF_SIZE && m_last._Myres >= m_last._BUF_SIZE">{m_first._Bx._Ptr,sb} {m_last._Bx._Ptr,sb}</DisplayString> <Expand> <Item Condition="m_first._Myres < m_first._BUF_SIZE" Name="First">m_first._Bx._Buf,sb</Item> <Item Condition="m_first._Myres >= m_first._BUF_SIZE" Name="First">m_first._Bx._Ptr,sb</Item> <Item Condition="m_last._Myres < m_last._BUF_SIZE" Name="Last">m_last._Bx._Buf,sb</Item> <Item Condition="m_last._Myres >= m_last._BUF_SIZE" Name="Last">m_last._Bx._Ptr,sb</Item> </Expand> </Type>
虽然这些可视化效果在一定意义上可以在监视窗口中产生所需的无杂波输出,但它们需要更多的工作来编写和维护。 首先,CNameList和CName的可视化工具现在都依赖于STL中类的私有成员。 由于STL的实现细节可能会发生更改,如果STL实现更改了这些条目所依赖的内容,这些可视化工具就有可能无法在未来版本的visualstudio中工作。 此外,如果CNameList作为头文件分发,并且可能包含在任何版本的visualstudio中,那么对于STL的每个实现,您可能需要为CName包含一个单独的natvis条目,然后必须在将来CName的实现发生更改的任何时候更新所有条目。
此外,当内部类的可视化工具中包含条件时,条件最终会以使可视化工具一团糟的方式成倍增加。 例如,std::basicu string的内置可视化工具有两种可能的显示字符串情况:
<Type Name="std::basic_string<char,*>"> <DisplayString Condition="_Myres < _BUF_SIZE">{_Bx._Buf,s}</DisplayString> <DisplayString Condition="_Myres >= _BUF_SIZE">{_Bx._Ptr,s}</DisplayString> <StringView Condition="_Myres < _BUF_SIZE">_Bx._Buf,s</StringView> <StringView Condition="_Myres >= _BUF_SIZE">_Bx._Ptr,s</StringView> <Expand> <Item Name="[size]">_Mysize</Item> <Item Name="[capacity]">_Myres</Item> <ArrayItems> <Size>_Mysize</Size> <ValuePointer Condition="_Myres < _BUF_SIZE">_Bx._Buf</ValuePointer> <ValuePointer Condition="_Myres >= _BUF_SIZE">_Bx._Ptr</ValuePointer> </ArrayItems> </Expand></Type>
但是,由于CName同时包含名字和姓氏,因此根据名字和姓氏是包含在.u Bx.u Buf还是.u Bx.u Ptr中,现在有四种情况,而不是两种情况。 如果我们也要增强CName以存储中间名,那么现在可视化工具将最多有8个案例,因为您要显示的每个新名称的案例数都会增加一倍。所以我们想提供一个更干净的方法。
在Visual Studio 2013中,您可以通过如下方式编写可视化工具,在“监视”窗口中实现CNameList的整洁视图:
<?xml version="1.0" encoding="utf-8"?><AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="CNameList"> <Expand> <ExpandedItem>m_list,view(simple)na</ExpandedItem> </Expand> </Type> <Type Name="CName"> <DisplayString>{m_first,sb} {m_last,sb}</DisplayString> <Expand> <Item Name="First">m_first,sb</Item> <Item Name="Last">m_last,sb</Item> </Expand> </Type></AutoVisualizer>
此视图可视化通过利用VisualStudio2013中的三个新natvis功能来工作:对象的多个视图、用于抑制指针内存地址的格式说明符,以及格式说明符跨多个natvis条目传播的能力。接下来让我们检查这三个。
在Visual Studio 2012中,
在VisualStudio2013中,每种类型仍然只有一个默认视图,现在natvis条目可以定义其他视图,这些视图可以通过适当的格式说明符访问。 例如,std::vector的Visual Studio 2013可视化如下所示:
<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>
始终使用
如果要将natvis元素添加到某个特定视图中,而不是从该视图中删除,则可以使用“IncludeView”属性,而不是“ExcludeView”。如果希望属性应用于一组视图而不是一组视图,还可以在“IncludeView”和“ExcludeView”属性中指定以分号分隔的视图列表。例如,在其他情况下,此可视化将使用“、视图(Alternate)”或“、视图(alternate2)”和“默认视图”显示“备用视图”的显示文本。
<Type Name="MyClass"> <DisplayString IncludeView="alternate; alternate2">Alternate view </DisplayString> <DisplayString>Default View</DisplayString> </Type>
因此,回到我们的示例,我们的CNameList可视化工具利用“vector”可视化工具中定义的“simple”视图来消除大小和容量节点的混乱:
<Type Name="CNameList"> <Expand> <ExpandedItem>m_list,view(simple)na</ExpandedItem> </Expand> </Type>
Visual Studio 2013添加了一个新的格式说明符“,na”。当应用于指针时,“-na”格式说明符会导致调试器忽略指向的内存地址,同时仍保留有关对象的信息。例如:
在我们的CNameList示例中,我们使用“.na”格式说明符来隐藏CName对象的内存地址,这些地址并不重要。如果没有“na”格式说明符,隐藏内存地址将需要复制粘贴和修改std::vector的可视化工具,使其取消对向量内部元素的引用,如下所示:
<Type Name="CNameList"> <Expand> <IndexListItems> <Size>m_list._Mylast - m_list._Myfirst</Size> <ValueNode>*m_list._Myfirst[$i]</ValueNode> </IndexListItems> </Expand> </Type>
在我们的CNameList示例中,我们使用“.na”格式说明符来隐藏CName对象的内存地址,这些地址并不重要。如果没有“na”格式说明符,隐藏内存地址将需要复制粘贴和修改std::vector的可视化工具,使其取消对向量内部元素的引用,如图所示。
还应注意,“-na”格式说明符与解引用运算符“*”不完全相同。即使“na”格式说明符将省略所指向的数据的内存地址,但仍将显示有关该地址的任何可用符号信息。例如,在函数情况下,“*wmain”将是一个语法错误,但“wmain,na”显示“wmain”函数的模块和签名,省略了内存地址。类似地,“&myGlobal,na”仍然显示指针指向符号“int myGlobal”。“na”格式说明符也可以用于函数中间的内存地址,如“(void*)eip,na”示例所示。这可以使“,”na“格式说明符对于可视化对象内部记录的堆栈跟踪非常有吸引力,以便进行调试。
尽管Visual Studio 2012中已存在“.sb”格式说明符,但像这样编写CName visualizer在VS2012中不起作用:
<Type Name="CName"> <DisplayString>{m_first,sb} {m_last,sb}</DisplayString> <Expand> <Item Name="First">m_first,sb</Item> <Item Name="Last">m_last,sb</Item> </Expand> </Type>
原因是“mu first”实际上不是char*,而是std::basicu字符串。因此,VisualStudio2012实际上从std::basic_字符串可视化工具(而不是CName可视化工具)获取底层char*的格式说明符。虽然“m_first,sb”仍然是合法的语法,但在visualstudio2012下,CName的可视化工具中的“sb”实际上被完全忽略了。
同时,由于std::basicu字符串的可视化工具是为处理常见情况而设计的,因此std::basicu字符串使用“s”,而不是“sb”,导致包含引号。因此,当你的意图是脱光衣服的时候,他们实际上还在那里。在Visual Studio 2012中,唯一不更改std::basicu字符串,也不可能破坏与CName无关的其他可视化效果的解决方法是将std::basicu字符串的内容内联到CName的可视化工具中,因此与“.sb”一起使用的字符串实际上是一个直接字符*,而不是std::basicu字符串。
在VisualStudio2013中,用于可视化对象的格式说明符与对象的可视化工具本身的格式说明符合并,而不是被抛出。换言之,在Visual Studio 2013中,“mu first,sb”中的“b”会传播到std::basicu字符串可视化工具中显示的字符串,从而可以很好地轻松地去除引号,而无需修改或内联std::basicu字符串可视化工具。
另一个传播格式说明符的例子是我们新的CNameList可视化工具。即使Visual Studio 2012中确实存在“na”格式说明符,如果没有格式说明符的传播,“mu list,na”仍然不起作用,因为由于std::vector的可视化工具没有使用“na”,因此会忽略“na”。在VisualStudio2013中,“na”格式说明符会自动传播到向量的元素,一切正常。
格式说明符传播的另一个好例子是以十六进制显示整数集合的元素。visualstudio 2012中已经存在用于以十六进制显示整数的“.x”格式说明符,但仅当它直接应用于整数值时才存在。当应用于向量对象时,Visual Studio 2012将忽略它,如下所示:
在VisualStudio2012中,以十六进制显示向量元素需要修改std::vector的可视化工具,以便 每一个 vector的元素是十六进制的,或者切换全局的“十六进制显示”选项,这将导致每个监视项都是十六进制格式,而不仅仅是一个vector。
在Visual Studio 2013中,“,x”只是自动向下传播到向量的子级,如下所示:
虽然上述特性是使我们的CNameList示例正常工作所必需的,但是还有一些其他与natvis相关的改进,也值得一提:
在显示字符串中使用类的最终实现名称:
在visualstudio2013中,基类的natvis条目可以使用对象实现类的名称,即元素中的$(Type)宏。例如,如果我们有以下源代码:
class Room{private: int m_squareFeet;public: Room() : m_squareFeet(100) {} virtual void Print() = 0;};class Bedroom : public Room{public: virtual void Print() { printf("Bedroom"); }};class LivingRoom : public Room{public: virtual void Print() { printf("Living room"); }};class DiningRoom : public Room{public: virtual void Print() { printf("Dining room"); }};int _tmain(int argc, _TCHAR* argv[]){ Bedroom br; LivingRoom lr; DiningRoom dr; br.Print(); lr.Print(); dr.Print();}
我们可以为类“Room”编写一个可视化工具,如下所示:
<?xml version="1.0" encoding="utf-8"?><AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="Room"> <DisplayString>{m_squareFeet}-square foot $(Type)</DisplayString> </Type></AutoVisualizer>
将显示我们拥有的房间类型,如下所示:
在Visual Studio 2012中,实现此显示需要创建一个单独的
注意$(Type)的使用是区分大小写的$(类型)将不起作用。另外,使用$(Type)要求基类至少包含一个虚拟函数,因为如果不存在vtable,调试器就无法知道对象的实现类实际上是什么。
支持循环链表
在Visual studio 2013中,
class CircularList{private: struct Node { int m_value; Node* m_pNext; }; Node* m_pFirst; Node* GetTail() { if (!m_pFirst) return NULL; Node* pNode = m_pFirst; while (pNode->m_pNext != m_pFirst) pNode = pNode->m_pNext; return pNode; }public: CircularList() : m_pFirst(NULL) {} ~CircularList() { Node* pNode = m_pFirst; while (pNode != m_pFirst) { Node* pNext = pNode->m_pNext; delete pNode; pNode = pNext; } } void AddTail(int i) { Node* pNewNode = new Node(); if (m_pFirst) GetTail()->m_pNext = pNewNode; else m_pFirst = pNewNode; pNewNode->m_value = i; pNewNode->m_pNext = m_pFirst; }};int _tmain(int argc, _TCHAR* argv[]){ CircularList list; list.AddTail(1); list.AddTail(2); list.AddTail(3); return 0;}
我们可以用一个简单的元素来显示’list’的值,如下所示:
<?xml version="1.0" encoding="utf-8"?><AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="CircularList"> <Expand> <LinkedListItems> <HeadPointer>m_pFirst</HeadPointer> <NextPointer>m_pNext</NextPointer> <ValueNode>m_value</ValueNode> </LinkedListItems> </Expand> </Type></AutoVisualizer>
在Visual Studio 2013中,输出如下:
在Visual Studio 2012中,该列表将被假定为永远存在,因为列表节点的“next”指针从不为空,因此产生如下输出:
VisualStudio2013中的循环列表支持仅适用于元素的“下一个”指针直接指向列表头的情况。如果一个“下一个”指针指向列表中的前一个节点,而该节点不在最前面,那么VisualStudio2013仍然会将该列表视为永远继续,就像VisualStudio2012一样。
因为“next”指针表达式确实可以访问基础列表对象中的字段,所以对于VisualStudio2012来说,没有合理的解决方法。
VisualStudio2013试图解决natvis框架中的缺陷要求在对象的可视化视图的质量和/或其背后的natvis条目的可维护性方面妥协的最常见情况。有关此功能或任何其他诊断相关功能的反馈,请访问 MSDN论坛 我也期待你的评论。