1 autoexp.dat的基本介绍
微软的VS系列编译器为用户提供了强大的调试功能,除了经常用到的Watch,memory等可以用于查看变量信息的窗口外,其实微软还提供了一个可以用于配置显示信息内容及信息显示格式的文件——autoexp.dat。autoexp.dat文件除了决定Watch窗口中变量的显示格式及在调试时,当鼠标移向代码编辑区中的变量,此时出现一个tip窗口的显示格式之外,修改该文件还可直接配置原有类型的显示格式,甚至增添自定义类型的显示格式。
旧版本的VS对这项功能支持的并不是很好,多数时候对于自定义的类型无法辨认而只能显示“?”。在VS 2005,autoexp新增加了一个域[Visualizer],在该域中可以对基本类型,组合类型,自定义类型的显示进行配置,而最突出的一点是,该域支持一套语言标准,所有的配置必须用该语言进行描述。
微软并没有为[Visualizer]中所使用的语言发布正式的文件(至少我至今没有找到),而且在[Visualizer]的最前面写了一个大大的:DO NOTMODIFY。因此在动手之前还是得将原文件备份,千万不要像我一样。
该文件位于[VS的安装目录]\Common7\Packages\Debugger\。
2 浅谈[Visualizer](数据可视化)域的基本使用
对于autoexp.dat,经常被修改到的是[AutoExpand]域和[Visualizer]域。鉴于[Visualizer]域的强大,下面对[Visualizer]做简单的介绍及使用说明:
2.1 一般格式定义(Basic structure)
typename[|typename…]{ preview ( Preview-string-expression ) Stringview ( Text-visualizer-expression ) Children ( Expanded-contents-expression ) }
NOTE:
1、 typename指的是已有的基本类型、组合类型或自定义类型的名称,对于不同的类型,如果需要将它们的显示格式定义为相同的,可以使用“|”链接这些类型。
2、 格式的定义一般包含三个部分:preview、stringview及children。对于不需要的部分可以不用定义,这三个部分没有先后顺序之分。
下图是stringview、children和preview三个部分在调试时的显示。
1、 格式的定义的最外层用大括号“{ }”,其中的每个部分使用小括号“( )”。
2、 格式定义出错时,运行VS会弹出提示窗口,对于格式配置错误的类型,在调试期间无法正常显示。
3、 最外层的左边的大括号“{”必须紧挨着最后一个类型名typename,否则无论后面的格式正确与否,都无法正常显示。(没有看到特别的声明必须这样写,习惯性的喜欢把括号重新起行写,结果浪费了很多时间查找这个错误。不明白微软为什么要这样做?)
4、 符号“;”表注释。注释部分为“;”之后到本行结束的内容。
2.1.1 Preview
顾名思义,preview就是指预览。一般调试的时候,如果要查看一个结构体类型中变量的值,经常需要再多点击一次左边的“+”号展开,如果结构体中的变量少,多一次点击还是小事情,但是一旦遇上一个庞大的结构体,点击之后还要眯着眼睛细细的查找几个需要的变量,确实非常痛苦。
然而这种痛苦完全可以在preview中得到解决,方法很简单,把需要显示的变量写到preview中,每当鼠标移至该结构体对象或者将该结构体对象“Addwatch”时,就可以直接的获取这些变量的值。
Preview可以用于显示一个指定的字符串或者表达式或者字符串与表达式的组合。对于使用多个字符串或表达式进行的组合,需要在这些组合的最外层添加“#( )”。比如autoexp.dat中对于std::pair格式的显示:
std::pair<*>{ preview ( #( "(", $e.first, ",", $e.second, ")" ) ) }
其中,
1、 std::pair表示这是标准库中的pair类型。
2、 <*>为模板类型特有,表示所有的类型都适用。这里std::pair<*>为typename。
3、 “{”紧挨类型名pair,且该结构只定义preview一个部分。
4、 该preview包含三个字符串和两个指针,分别是”(”,”,”,”)”及用于指向pair类型第一个元素和第二个元素的指针。对于preview中的字符串或表达式,如果后面还有字符串或表达式,需要用逗号“,”相隔,最后一个字符串或表达式不需要逗号。(NOTE:格式的死规定,没有按照规定编写则调试时信息无法正常显示。)
2.1.2 Stringview
stringview主要用于将string使用Text、XML或HTML显示。
在autoexp.dat中的std::vector<*>{ }内添加如下代码:
stringview ( "Yuck!!!!" )
当点击放大镜图标时可以得到如下窗口:
点击倒三角图标,可以选择string的显示格式。
stringview块主要用于显示详细的内容,当调试满足设定的条件时,通过打开Visualizer窗口可以看到详细的信息。
2.1.3 Children
对于一个复杂的结构体(或者说类)定义,最主要的部分就数children这块了。和preview一样,如果children中需要定义多个字符串或者表达式的组合,同样需要在这些组合的最外层添加“#( )”。
Children部分的信息通常不会自动显示(除非在[AutoExpand]域进行修改),需要点击左边的“+”,之后按照[0],[1],[2]……的顺序展开详细列表。
同样,以autoexp.dat中对于vector迭代器的定义为例,如下:
std::_Vector_iterator<*>|std::_Vector_const_iterator<*>{ preview ( #(*$e._Myptr) ) children ( #(ptr:*$e._Myptr) ) }
其中:
1、 “|”用于连接两种迭代器:普通的vector迭代器和const vector迭代器,表示这两种迭代器在调试显示时的格式是一致的。
2、 该结构包含两个部分:preview和children。Preview只包含一个表达式,用于显示当前对象的值。其中*$e._Myptr的各个含义为:$e:指代一个元素;_Myptr是stl中自定义的指针,$e._Myptr表示取得该地址的一个元素,之后通过取值操作“*”获取该地址存取的值。因此*$e._Myptr表示的是该迭代器所指向的vector的第一个元素的值。
3、 children部分只包含一个表达式,用于显示当前对象的值。ptr只是给该值起的一个名字,换成mylove也是OK的。
2.2 特殊结构
前面说过,在[Visualizer]域中进行配置的格式,其实就是一种语言,语言除了语法,当然免不了的还有一些基本的结构,对于这些结构,我把所知道的归纳为特殊结构。
2.2.1 array
这里说的array和一般编程语言中的array有些许不同,编程语言中的array指的是一种数组的集合,而这里的array指的是对该类型可能存在的集合按顺序进行列举,#array的含义更多的是一种动态的行为。
以autoexp.dat中对vector类型配置为例:
std::vector<*>{ children ( #array ( expr : ($c._Myfirst)[$i], size : $c._Mylast-$c._Myfirst ) ) preview ( #( "[", $e._Mylast- $e._Myfirst , "](", #array ( expr : ($c._Myfirst)[$i], size : $c._Mylast-$c._Myfirst ), ")" ) ) }
经由该结构解析得到的调试信息如下:
该结构只定义了preview和children,所以没有stringview的显示。前面提到的$e表示的是一个元素,而$c表示的则是一个数据结构,这里的$c指的是vector<int>。$i表示索引值,在“#array( )”结构中,$i值会自动递增,因此相当于将该vector进行一次遍历。
在children中,通过$i的递增将vector中的元素列举。
在preview中,定义的是字符串和表达式的组合,因此需要用逗号进行分隔。
NOTE:
1、 任何由#array定义的列举,无论是否命名,该命名都是无效的。#array会自动在该值前面加上递增序列[0],[1],[2]……。
2、 任何array结构都必须同时包含expr和size两个部分,缺少其中一个部分都将导致信息无法正确显示。
2.2.2 list
list结构类型和“#array( )”类似,所有list的结构定义都被放置在“#list( )”中。
以autoexp.dat中对list类型配置为例:
std::list<*>{ children ( #list ( head : $c._Myhead->_Next, size : $c._Mysize, next : _Next ) : $e._Myval ) preview ( #( "[", $e._Mysize, "](", #list ( head :$c._Myhead->_Next, size : $c._Mysize, next : _Next ) : $e._Myval, ")" ) ) }
经由该结构解析得到的调试信息如下:
2.2.3 Tree
自从有了array和list,世界仿佛变得清晰了很多,但是只有这两个是远远不够的,因为结构体里面最邪恶的并不是规规矩矩的数组或链表,而是Tree。
对于Tree的支持,同样提供了一个类似的结构“#tree( )”。
以autoexp.dat中对map类型配置为例:
std::map<*>{ children ( #tree ( head :$c._Myhead->_Parent, skip : $c._Myhead, size : $c._Mysize, left : _Left, right : _Right ) : $e._Myval ) preview ( #( "[", $e._Mysize, "](", #tree ( head :$c._Myhead->_Parent, skip : $c._Myhead, size : $c._Mysize, left : _Left, right : _Right ) : $e._Myval, ")" ) ) }
经由该结构解析得到的调试信息如下:
2.2.4 Conditional
除了前面提到的特殊类型,[Visualizer]还支持对条件语句的判断。这种类型看起来和预编译中的一些条件语句很相像(不过应该不是预编译,因为这里的[Visualizer]并不支持sizeof)。
这里的条件语句主要有#if…#else和#switch…#case…#default。
比如autoexp.dat中对base_string结构的配置(只摘取一部分)对#if的使用:
std::basic_string<char,*>{ preview ( #if(($e._Myres) <($e._BUF_SIZE)) ( [$e._Bx._Buf,s] ) #else ( [$e._Bx._Ptr,s] ) ) ;…… }
下面是autoexp.dat中对结构的配置(只摘取一部分)对#switch的使用:
tagPROPVARIANT|tagVARIANT|PROPVARIANT|VARIANT{ preview( #switch($e.vt) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;Base Types ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #case0 ( #("Empty") ) ;VT_EMPTY #case1 ( #("NULL") ) ;VT_NULL #case2 ( #("I2 = ", $e.iVal) ) ; VT_I2 #case3 ( #("I4 = ", $e.lVal) ) ;…… }
NOTE:
1、 #switch语句中不需要“break”,执行完每个#case后会自动跳出。
2、 #case没有一个明确的范围,其作用域在上一个#case结束之后和下一个#case结束之前,最后一个#case的作用范围在上一个#case的结束之后和#default的开始之前(这点需要确定)。
3、 #switch结构不能用于#array结构中,否则可能导致VS挂死。因此,如果在#array中需要使用到#switch,可以使用#if代替。