美化显示MSVC调试的数据结构
笔者在前面博文
美化显示GDB调试的数据结构中有提到:打算以mupdf库为例,介绍GDB
、LLDB
和MS Debugger
这三个调试器的美化输出,目前已经写了两篇了:
美化显示GDB调试的数据结构和
美化显示LLDB调试的数据结构。
本文将介绍MS调试器的美化显示,还是以mupdf库为例,先看一下效果:
MS调试器不像GDB与LLDB支持Python脚本,它使用的是一种扩展名为.natvis
的XML文件格式,规则相对简单,但功能也相对较弱。.natvis
的文档参见:
使用 Natvis 框架在调试器中创建自定义C++对象的视图
根据规则,.natvis
文件可以放在项目的根目录,比如取名为mupdf.natvis
。
框架为:
1<?xml version="1.0" encoding="utf-8"?>
2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3 <Type Name="pdf_obj">
4 </Type>
5</AutoVisualizer>
这里没有如GDB和LLDB中的Python脚本需要自己写正则表达式或者其它代码来匹配类型,直接给出原型类型pdf_obj
,调试器的.natvis
解析引擎会自动匹配类型。
需要注意的是.natvis
虽然可以调用函数,但是调试器不会主动执行有副作用的函数,同时不能像Python一样保存临时变量,所以针对mupdf库,必须要有调试信息,即必须知道完整的数据结构,否则无效。
接下来就是针对pdf_obj封装的各种不同类型(整数(int)
、浮点数(real)
、字符串(string)
、名字(name)
、数组(array)
、字典(dictinary)
、间接引用(indirect reference)
)进行处理。
一、基本类型
先介绍一个基础类型整数(int)
的美化显示方法:
1<?xml version="1.0" encoding="utf-8"?>
2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3 <Type Name="pdf_obj">
4 <!--整数-->
5 <DisplayString Condition="(kind=='i')">
6 {((pdf_obj_num*)this)->u.i}
7 </DisplayString>
8 </Type>
9</AutoVisualizer>
在.natvis
中使用this
来表示调试查看的变量,该this
不是C++的this
,纯C语言中的数据结构也是使用this
来表示调试查看的变量。由于pdf_obj是一个封装类型,实际类型是根据kind
来决定的:
1typedef enum pdf_objkind_e
2{
3 PDF_INT = 'i',
4 PDF_REAL = 'f',
5 PDF_STRING = 's',
6 PDF_NAME = 'n',
7 PDF_ARRAY = 'a',
8 PDF_DICT = 'd',
9 PDF_INDIRECT = 'r'
10} pdf_objkind;
上述的.natvis
表示类型为pdf_obj
的变量,如果kind
为i
则使用{((pdf_obj_num*)this)->u.i}
的值显示,注意一定要加大括号才会计算表达式的值。
二、容器类型
再介绍一个容器类型数组
的美化显示方法,容器类是需要展开的,所以需要添加到Expand
标签下:
1<?xml version="1.0" encoding="utf-8"?>
2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3 <Type Name="pdf_obj">
4 <Expand>
5 <!--数组-->
6 <Item Name="[cap]" Condition="(kind=='a')">((pdf_obj_array*)this)->cap</Item>
7 <Item Name="[len]" Condition="(kind=='a')">((pdf_obj_array*)this)->len</Item>
8 <ArrayItems Condition="(kind=='a')">
9 <Size>((pdf_obj_array*)this)->len</Size>
10 <!--数组子项不显示地址-->
11 <ValuePointer>((pdf_obj_array*)this)->items, na</ValuePointer>
12 </ArrayItems>
13 </Expand>
14 </Type>
15</AutoVisualizer>
这里就不一一介绍了标签作用了,以可参考文档,这里的数组显示了一个容量cap
和使用长度len
,然后就是展开的子项数据,子项数据中需要使用na
格式化参数来不显示地址,否则会比较难看。
三、间接引用类型
在mupdf中有间接引用(indirect reference)
类型,它指向的是另一个pdf_obj
对象。
由于mupdf中的绝大部分API都需要一个上下文fz_context
的指针,而在.natvis
中是无法保存临时变量的,所以不能像GDB与LLDB中的Python脚本那样调用fz_new_context_imp
来保存一个上下文。这里笔者采用一个变通的方法,即像在GDB与LLDB中的Python脚本中获取mupdf库的版本号一样,自定义一个函数将间接引用的对象转换出来:
1
2#if defined(_MSC_VER) && defined(_DEBUG)
3// VC调试器需要ref2obj辅助函数来展开查看间接引用的值
4fz_context* mupdf_ctx = nullptr;
5
6pdf_obj* ref2obj(pdf_obj* obj) {
7 if (mupdf_ctx == nullptr) {
8 return obj;
9 }
10 if (pdf_is_indirect(mupdf_ctx, obj)) {
11 return pdf_resolve_indirect_chain(mupdf_ctx, obj);
12 }
13 return obj;
14}
15#endif
这样就可以在.natvis
中调用了:
1 <Expand>
2 <!--间接引用展开显示,调用自定义函数ref2obj,调试器会报`此表达式有副作用,将不予计算`,需要手动刷新计算-->
3 <Item Name="value" Condition="(kind=='r')"> ref2obj(this) </Item>
4 </Expand>
四、完整版本
重要的技术点前面已经介绍了,下面直接给出完整的mupdf.natvis
配置:
1<?xml version="1.0" encoding="utf-8"?>
2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3 <Type Name="pdf_obj">
4 <!--整数-->
5 <DisplayString Condition="(kind=='i')">
6 {((pdf_obj_num*)this)->u.i}
7 </DisplayString>
8 <!--浮点数-->
9 <DisplayString Condition="(kind=='f')">
10 {((pdf_obj_num*)this)->u.f}
11 </DisplayString>
12
13 <!--字符串-->
14 <DisplayString Condition="(kind=='s')">
15 <!--以UTF8格式显示字符串-->
16 {{ text = {((pdf_obj_string*)this)->text,s8}, buf = {((pdf_obj_string*)this)->buf,s8} }}
17 </DisplayString>
18
19 <!--布尔的false-->
20 <!--this == ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE))-->
21 <DisplayString Condition="this == ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE)">
22 false
23 </DisplayString>
24
25 <!--布尔的true-->
26 <!--this == ((pdf_obj*)(intptr_t)PDF_ENUM_TRUE))-->
27 <DisplayString Condition="this == ((pdf_obj*)(intptr_t)PDF_ENUM_TRUE)">
28 true
29 </DisplayString>
30
31 <!--名字-->
32 <!--this > ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE) && this < ((pdf_obj*)(intptr_t)PDF_ENUM_LIMIT)-->
33 <DisplayString Condition="this > ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE) && this < ((pdf_obj*)(intptr_t)PDF_ENUM_LIMIT)">
34 <!--以UTF8格式显示不带引号的字符串-->
35 /{PDF_NAME_LIST[(intptr_t)this],s8b}
36 </DisplayString>
37
38 <!--名字-->
39 <DisplayString Condition="(kind=='n')">
40 <!--以UTF8格式显示不带引号的字符串-->
41 /{((pdf_obj_name*)this)->n,s8b}
42 </DisplayString>
43
44 <!--间接引用-->
45 <DisplayString Condition="(kind=='r')">
46 {{ num = {((pdf_obj_ref*)this)->num}, gen = {((pdf_obj_ref*)this)->gen} }}
47 </DisplayString>
48
49 <Expand>
50 <!--数组-->
51 <Item Name="[cap]" Condition="(kind=='a')">((pdf_obj_array*)this)->cap</Item>
52 <Item Name="[len]" Condition="(kind=='a')">((pdf_obj_array*)this)->len</Item>
53 <ArrayItems Condition="(kind=='a')">
54 <Size>((pdf_obj_array*)this)->len</Size>
55 <!--数组子项不显示地址-->
56 <ValuePointer>((pdf_obj_array*)this)->items, na</ValuePointer>
57 </ArrayItems>
58
59 <!--字典-->
60 <Item Name="[cap]" Condition="(kind=='d')">((pdf_obj_dict*)this)->cap</Item>
61 <Item Name="[len]" Condition="(kind=='d')">((pdf_obj_dict*)this)->len</Item>
62 <ArrayItems Condition="(kind=='d')">
63 <Size>((pdf_obj_dict*)this)->len</Size>
64 <!--字典子项不显示地址-->
65 <ValuePointer>((pdf_obj_dict*)this)->items, na</ValuePointer>
66 </ArrayItems>
67
68 <!--字符串展开显示-->
69 <Item Name="string" Condition="(kind=='s')">(pdf_obj_string*)this</Item>
70 <!--间接引用展开显示,调用自定义函数ref2obj,调试器会报`此表达式有副作用,将不予计算`,需要手动刷新计算-->
71 <Item Name="value" Condition="(kind=='r')"> ref2obj(this) </Item>
72 </Expand>
73 </Type>
74 <!--keyval结构-->
75 <Type Name="keyval">
76 <!--使用na参数不显示地址-->
77 <DisplayString>{{ {k,na},{v,na} }} </DisplayString>
78 <Expand>
79 <Item Name="Key"> k </Item>
80 <Item Name="Value"> v </Item>
81 </Expand>
82 </Type>
83 <!--fz_buffer结构-->
84 <Type Name="fz_buffer">
85 <DisplayString>{data,[len]s8}</DisplayString>
86 <Expand>
87 <Item Name="[cap]">cap</Item>
88 <Item Name="[len]">len</Item>
89 <!--按指定长度显示文本-->
90 <Item Name="text">data,[len]s8</Item>
91 </Expand>
92 </Type>
93</AutoVisualizer>
测试代码t.c
:
1#include <mupdf/fitz.h>
2#include <mupdf/pdf.h>
3#include <stdbool.h>
4
5#ifndef __cplusplus
6#if __STDC_VERSION__ < 202311L
7#ifdef nullptr
8#undef nullptr
9#endif
10
11#define nullptr NULL
12#endif
13#endif
14
15// 为GDB调试器使用,不能设置为static
16// 这将使得GDB可以在运行时获取mupdf的版本信息
17const char* mupdf_version = FZ_VERSION;
18
19#if defined(_MSC_VER) && defined(_DEBUG)
20// VC调试器需要ref2obj辅助函数来展开查看间接引用的值
21fz_context* mupdf_ctx = nullptr;
22
23pdf_obj* ref2obj(pdf_obj* obj) {
24 if (mupdf_ctx == nullptr) {
25 return obj;
26 }
27 if (pdf_is_indirect(mupdf_ctx, obj)) {
28 return pdf_resolve_indirect_chain(mupdf_ctx, obj);
29 }
30 return obj;
31}
32#endif
33
34int main(int argc, char* argv[]) {
35 const char* p = "hello";
36 bool b = true;
37 pdf_obj* nil = nullptr;
38 fz_context* ctx = fz_new_context(nullptr, nullptr, FZ_STORE_UNLIMITED);
39#if defined(_MSC_VER) && defined(_DEBUG)
40 mupdf_ctx = ctx;
41#endif
42 pdf_document* doc = nullptr;
43 fz_try(ctx) {
44 // 由于目前的VS2022 CMake项目即使配置了"cwd": "${workspaceRoot}"也不会是项目根目录,
45 // 所以文件路径根据情况自行设定。
46 doc = pdf_open_document(ctx, "../../../t.pdf");
47 }
48 fz_catch(ctx) {
49 fz_report_error(ctx);
50 printf("cannot open document\n");
51 fz_drop_context(ctx);
52 return EXIT_FAILURE;
53 }
54
55 pdf_obj* Int = pdf_new_int(ctx, 10);
56 pdf_obj* Real = pdf_new_real(ctx, 3.14F);
57 pdf_obj* Str = pdf_new_text_string(ctx, "hello");
58 pdf_obj* Name = pdf_new_name(ctx, "name");
59 pdf_obj* True = PDF_TRUE;
60 pdf_obj* False = PDF_FALSE;
61
62 pdf_obj* ar = pdf_new_array(ctx, doc, 10);
63 pdf_array_put(ctx, ar, 0, Int);
64 pdf_array_put(ctx, ar, 1, Real);
65 pdf_array_put(ctx, ar, 2, Str);
66 pdf_array_push_bool(ctx, ar, 1);
67 pdf_array_push_bool(ctx, ar, 0);
68 pdf_array_push(ctx, ar, PDF_NULL);
69
70 pdf_obj* dict = pdf_new_dict(ctx, doc, 10);
71 pdf_dict_puts(ctx, dict, "int", Int);
72 pdf_dict_puts(ctx, dict, "real", Real);
73 pdf_dict_puts(ctx, dict, "str", Str);
74 pdf_dict_puts(ctx, dict, "name", Name);
75 pdf_dict_puts(ctx, dict, "array", ar);
76
77 pdf_obj* ref = pdf_new_indirect(ctx, doc, 3633, 0);
78
79 pdf_drop_obj(ctx, Int);
80 pdf_drop_obj(ctx, Real);
81 pdf_drop_obj(ctx, Str);
82 pdf_drop_obj(ctx, Name);
83 pdf_drop_obj(ctx, ar);
84 pdf_drop_obj(ctx, dict);
85 pdf_drop_obj(ctx, ref);
86
87 pdf_drop_document(ctx, doc);
88 fz_drop_context(ctx);
89}
笔者可能会持续改进与补充,欲知后续版本,请移步: https://github.com/WittonBell/demo/blob/main/mupdf/mupdf.natvis
整个测试项目地址: https://github.com/WittonBell/demo/blob/main/mupdf
如果本文对你有帮助,欢迎点赞收藏!
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2025/2025-06-13-美化显示MSVC调试的数据结构/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。