另一个枚举查看器






4.50/5 (2投票s)
2001年10月23日
5分钟阅读

85461

1284
关于另一个枚举查看器用法和设计的文章
另一个枚举查看器
本文解释了枚举查看器工具的用法和开发。用法
将枚举查看器的 .exe 文件下载到同一个目录。运行 enum_viewer。打开一个头文件。单击列按钮进行排序。开发
1. 问题
我的日常工作是编写通过 Win32 通信通道(rs232、rs485、tcp/ip 等)与嵌入式设备通信的软件。为了最大限度地提高向后兼容性和协议灵活性,我们设计了一个使用长度和标识符的消息传递协议。消息都包含一个在头文件中定义的 16 位 `enum
` 值的唯一标识符。我正在编写一个设备模拟器,并且想要在查看原始消息时能够看到 `enum
` 值。我见过的所有工具都无法处理注释、`#defines`、类中的枚举、带有 `enum
` 赋值的枚举等。Visual C++ IDE、浏览器和调试器都不显示 `enum
` 值(如果 `typedef'
` 了,调试器一次只能显示一个)。
2. 解决方案
我想肯定有一些 C++ 解析器可以作为枚举查看器的基础。在 Google 上快速搜索发现了一个名为 John's PCCTS-based C++ Parser Page 的页面。
太棒了!一个 C++ 解析器!我下载并尝试编译代码,但编译因有关 STL 分配器的错误消息而停止。在尝试让解析器使用 `STLPort` 无果后,我使用了默认的 MSVC STL,它就能成功编译。
然后我转向了文档(你们中的一些人可能正在窃笑,别笑了)。查阅文档后,我感到有些沮丧,并短暂地迷恋上了正则表达式。思考声明/定义枚举的所有方式让我又回到了解析器。再次查看目录时,我注意到一个名为 cpp_test 的示例应用程序。我编译并运行它,针对一个包含许多枚举的头文件。结果令人鼓舞,所有枚举的名称及其递归值 `eRED = (((10)+1)+1)` 都显示了出来。
深入研究代码,我找到了转储函数,并通过 Joshua C. Jensen 非常出色的 Workspace Whiz 查找了各种类成员的定义。
我只需要派生两个新类,将一个函数从 private 移到 protected,然后我就可以得到一个命令行应用程序,可以将头文件转换为一个包含枚举及其值的 ASCII 文件。
3. 显示数据
尽管这很酷,但我仍然想对枚举名称或其值进行排序。我想要一个能够浏览、排序和以十六进制显示数据的 GUI。嗯……在过去几年里,我做了大量使用 ListView 控件的工作,这似乎是一个不错的选择(正如俗话说的,手里拿着锤子,看什么都像钉子)。我生成了一个 AppWizard MFC SDI 应用程序,并开始处理逗号分隔值文件的解析。
出于以下三个原因,我决定让 ListView 成为虚拟的
- 更灵活,所有格式化都在显示时完成
- 更少的内存占用
- 排序速度更快
虚拟 ListView 的全部目的是让应用程序在内存中维护 ListView 的字符串表示。为此,我使用了一个二维 `std::vector` of `CString` 来缓存文档中的数据。我使用 `WM_NOTIFY` 消息 `LVN_ODCACHEHINT` 来初始化缓存的大小和内容。ListView 通过 `GetBuffer()` 向 `WM_NOTIFY` 消息 `LVN_GETDISPINFO` 传递指向缓存的 `CString` 缓冲区的指针来响应。我本可以使用 `std::string`,但由于 `CString::Format` 的简单调用即可实现整数到十进制和十六进制的转换,而无需使用 `std::stringstream`。有关更多详细信息,请参阅 enum_view.cpp 中的代码。在确定了视图之后,我继续实现文档。
虚拟视图需要两件重要的事情:用于初始化的行数以及用于填充缓存的单个行访问。
计算行数
ListView 中的每一行都对应解析器输出文件中的一行。每一行看起来是这样的
enum name,value\r\n
因此,文档中 \n 的数量就是记录的数量。使用 C++ 标准库可以很容易地做到这一点
size_t lineCount = 0; // ignore all characters up to '\n', extract '\n', toss it, advance while ( m_fileIn.ignore(std::numeric_limits::max(), '\n') ) ++lineCount;
其中 m_fileIn 是任何 istream。
单个行访问
对于 C++ iostreams 来说,这很简单。使用 `iostream` 提取器的一个好处是,可以通过对提取本身进行布尔测试来推断提取的状态。有关详细信息,请参阅 Marshall Cline 的优秀 C++ FAQ。
// in case the stream was in an error state (at the end of file for example) m_fileIn.clear(); // using an index is waaaaay faster. m_fileIn.seekg( fileOffset ); std::string strName(""); parsed = std::getline(m_fileIn, strName, ',') && m_fileIn >> value; // Convert std::string into CString if (parsed) name = strName.c_str();
在我第一次获取记录时,我将文件定位到开头,然后向上计数到指定的索引。这使得视图非常慢!我通过创建一个文件偏移量的索引并在需要时定位到它们,极大地加快了速度。
然后,我将打开文件的请求连接起来,以启动枚举解析器的子进程,并将一个临时文件名传递给它。
排序
我在 C/C++ Users Journal 上读到了一篇关于通用 STL 索引创建函数的非常有用的技巧。Herb Sutter 在 Guru of the Week 上对代码进行了很好的源代码审查,并进行了许多改进。我实现了一些,并添加了我自己的一个(请参阅代码了解详细信息)。`create_index` 函数接收_任何_迭代器序列,对其进行排序,并填充排序结果的索引号。视图会保留一个这样的索引序列,并使用它来显示数据。如果当前列已排序,则只需反转索引顺序。这一切都归功于美妙的正交 STL。
最近使用的文件
由于 `CDocument` 使用临时文件,MRU 列表会收到所有这些不再存在的临时文件名。应用程序会删除这些文件,而是添加头文件名。
就是这样。如果您有任何疑问,请查看代码。如果您发现任何错误,也请告诉我。我甚至可能修复它们。 =)
另外,如果要在 MFC 或标准 C++ 库方法之间进行选择,我更倾向于使用标准方法。我的理由是
- 标准库的第三方文档更好(我宁愿选择 Austern、Josuttis 等人,也不愿选择任何 MFC 作者)
- 标准库接口更一致、更正交
- 更灵活的平台/编译器支持