Win32 SDK 开发者的消息解析器向导






4.94/5 (101投票s)
一个类似ClassWizard的辅助工具,用于使用WINDOWSX.H 消息解析器。
下载源代码和发布版
https://github.com/hernandp/MessageCrackerWizard
目录
引言
WINDOWS.H 头文件为 Win32 SDK 程序员提供的便利
许多初级和中级程序员在用 C/C++ 编程 Windows API 时,经常会面临意大利面条式的 switch...case
代码块问题。当你在窗口过程中添加大量消息需要捕获时,比如查找 WM_COMMAND
或 WM_CHAR
,代码块就会变成一场噩梦。
C/C++ 7.0 编译器和 Windows 3.1 版软件开发工具包以来提供的头文件可以解决千行窗口过程的问题。这个头文件是 <windowsx.h>,其中包含许多有用的宏。根据微软的说法,该头文件的功能可以归纳为以下几类:
- 使用
STRICT
宏对 C 程序进行更严格的类型检查。 - 简化 Windows 编程中许多常见操作的宏。
- 简化与 Windows 控件通信的控制宏。
- 消息解析器(这是处理消息的一种方便、可移植且类型安全的方法)及其在 Windows 环境中的相关参数和返回值。
由于 Message Cracker Wizard 旨在帮助使用消息解析器,因此我将跳过该头文件提供的其他有用宏。如果你想简要了解 WINDOWSX.H 文件能做什么,可以查阅微软知识库文章 #83456。
好了,让我们来介绍一下消息解析器的优点,当然,还有这里提供的工具在你代码中使用它们时为什么会很有用。
当你使用 Win32 SDK 编程时,你会通过一个窗口过程来处理窗口和对话框消息,通常命名为 WndProc
。在 Windows C 编程中,窗口过程使用 switch
关键字和一堆 case
标签来捕获我们想要处理的每条消息,这是非常常见的。
假设我们要为我们的主窗口处理 WM_COMMAND
、WM_KEYUP
、WM_CLOSE
和 WM_DESTROY
。我们可以编写一个具有如下逻辑的窗口过程:
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_COMMAND: // ... break; case WM_KEYUP: // ... break; case WM_CLOSE: // ... break; case WM_DESTROY: //... break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } }
这是自 Windows 1.0 时代以来处理窗口消息最常用的方式,当然,它有效。但问题在于,当你开始为程序添加越来越多的复杂功能时,例如 MDI、OLE、通用控件等,你就会得到一个千行窗口过程。你开始使用 PageDn 和 PageUp 键来查找你想要修改的消息。
这是使用消息解析器的第一个优点:它们将那种混乱的 case 标签转换成易于维护的处理函数,就像 MFC 一样。
第二个优点是你在处理函数中使用正确的参数格式。而不是做那些 switch(LOWORD(wparam))
,你可以简单地使用 switch(id)
,因为你提供的消息函数将 id
作为“已解析”参数之一传递,它等于 LOWORD(wparam)
。
消息处理宏 HANDLE_MSG
定义在 windowsx.h 中,如下所示:
#define HANDLE_MSG(hwnd, message, fn) \ case (message) : return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
正如你从上面的宏定义中所期望的那样,要将你的代码转换为“消息已解析”版本,你必须提供解析宏 HANDLE_MSG
和处理该消息的函数。HANDLE_MSG
宏放在窗口过程中。它需要三个参数:窗口句柄(hwnd
)、你要处理的消息(WM_xxxxx
)和我们将要编写来处理该消息的函数。为了更好地解释,上面窗口过程中的以下代码转换为消息解析器:
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG (hwnd, WM_COMMAND, OnCommand); HANDLE_MSG (hwnd, WM_KEYUP, OnKeyup); HANDLE_MSG (hwnd, WM_CLOSE, OnClose); HANDLE_MSG (hwnd, WM_DESTROY, OnDestroy); default: return DefWindowProc(hwnd, msg, wParam, lParam); } }
哇!这是一个更好、更紧凑、更容易管理的窗口过程。现在你可能想定义你的消息处理函数(OnKeyUp, OnClose
和 OnDestroy
)。这是一个真正的优势,因为你可以通过 Visual Studio IDE 跳转到你的消息处理函数!
问题在于,每次添加消息处理程序时,你都必须搜索 WINDOWSX.H 头文件的定义,并查找匹配消息处理函数的参数,因为你不能使用任意参数:处理函数的格式是明确的。在头文件中进行这种重复搜索可能会变得很乏味,并可能导致错误。Message Cracker Wizard 工具应运而生:它允许你为你想要处理的每个消息处理程序粘贴正确的函数参数。如果你是从头开始编写,它还可以编写一个模板窗口或对话框过程,以你将要处理的窗口消息为起点。
消息转发宏: XWINDOWS.H 的另一项功能
windowsx.h 头文件中的另一个有用功能是**消息转发**的可能性。这用于“解包”消息处理函数的参数,将其转换为合适的 WPARAM
和 LPARAM
值,以调用另一个期望 PostMessage
、SendMessage
、CallWindowProc
等参数的函数。
假设我们要使用 SendMessage
向父窗口发送一个 WM_COMMAND
消息,该消息通过发送一个通知代码 BN_DBLCLK
来“模拟”控件 IDC_USERCTL
的双击。你通常会这样做:
SendMessage (hwndParent, WM_COMMAND, MAKEWPARAM(IDC_USERCTL, BN_DBLCLK), (LPARAM)GetDlgItem(hwnd, ID_USERCTL));
这是一个相当复杂的语法:SendMessage
期望一个 WPARAM
参数,其中低位字是控件 ID,高位字是通知代码;LPARAM
参数是控件的句柄,我们在这里用 GetDlgItem
API 获取。
上面的代码可以转换为 WINDOWSX.H 的消息转发宏 FORWARD_WM_xxxxx
。对于每条消息,转发宏使用与 Message Cracker Wizard 创建的消息处理函数相同的“打包”参数,再加上你要调用的函数,并传递解包后的 LPARAM
/WPARAM
。例如,Message Cracker Wizard 将为 WM_COMMAND
消息和 myWnd
窗口 ID 生成以下函数原型:
void myWnd_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
嗯,这些已解析的参数与转发宏使用的参数相同——所以,正如你可能预期的那样,上面我们展示的令人困惑的 SendMessage
调用可以简化为:
FORWARD_WM_COMMAND (hwndParent, IDC_USERCTL, GetDlgItem(hwnd, ID_USERCTL), BN_DBLCLK, SendMessage);
这很简单,并且适用于所有 Message Cracker Wizard 支持的消息。
使用消息解析器向导工具
当你启动 Message Cracker Wizard 时,其界面如下所示:
向导在你左上角的列表框中提供了 WINDOWSX.H 处理的所有消息,你可以在其中单击一条或多条消息。Window ID 编辑框允许你为正在指定的窗口指定一个标识符。常见的 ID 有 MainWnd
、About
(用于关于对话框)等。这将在消息处理函数、HANDLE_MSG
宏以及如果你想从头开始创建窗口/对话框过程的名称中出现。“Make Window Procedure”(创建窗口过程)复选框允许你这样做:从头开始创建一个具有所有选定的消息解析器宏的窗口或对话框过程。当开始一个 Windows API 项目时,使用这种方法,你可以干净地编写和组织你的代码,当然,还可以避免错误。窗口底部的两个编辑框将包含生成的解析宏和处理这些消息的函数(仅限原型)。请注意,当你选中“Make Window Procedure”(创建窗口过程)时,窗口过程模板代码不会出现在这里:只有当你单击“Copy Macro”(复制宏)并将其粘贴到你的 C++ 编辑器时,它才会出现。
为了快速浏览 Message Cracker Wizard 工具的功能,我们以一个例子来演示。请记住,你必须通过在你的 .C / .CPP 文件中使用 #include <windowsx.h>
指令将 <windowsx.h> 头文件包含到你的项目中。
Message Cracker Wizard 功能快速导览
我们开始吧。假设你已经写了你的 WinMain
基本代码:你已成功填充了 WNDCLASS
结构,注册了窗口,并编写了一个功能正常的事件循环。现在你的主窗口需要一个窗口过程。
打开 Message Cracker Wizard。我们需要为窗口选择消息,因为 MCW 需要它来从头开始创建我们的主窗口过程。如你所知,Windows 程序处理 WM_CLOSE
、WM_DESTROY
和 WM_CREATE
消息非常普遍,所以让我们为这些消息构建带有消息解析器的窗口过程。之后,我们还将构建该窗口过程的消息处理函数的体。
在列表框中选择 WM_CLOSE
、WM_DESTROY
和 WM_CREATE
。由于该窗口将是我们程序的主窗口,请转到 Window ID 并输入 **main**。这是一个标识你的窗口/对话框的窗口 ID,它将作为解析宏和处理函数的后缀。当然,对于特定窗口的所有消息处理,你都会希望它保持一致。查看底部的编辑框。它们显示了 HANDLE_MSG
解析宏和相关消息处理函数的原型。
但是等等……我们说我们想要一个现成的窗口过程。所以点击“Make Window Procedure”(创建窗口过程)复选框,并确保选择了 **Window**(窗口)单选按钮。现在我们准备好了。请记住,**Dialog**(对话框)的工作方式与此相同,但会修改过程以使其成为对话框类型过程。
首先,我们需要源代码中的窗口过程。按“Copy Macro”(复制宏)按钮(或按 Ctrl-M),最小化向导(或将其放在手边,因为它保持在最顶层),然后转到你的 IDE,在你想要窗口过程的位置粘贴剪贴板的内容(Ctrl-V)。瞧!你将得到类似这样的代码:
// // main Window Procedure // LRESULT CALLBACK main_WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG (hwnd, WM_CLOSE, main_OnClose); HANDLE_MSG (hwnd, WM_CREATE, main_OnCreate); HANDLE_MSG (hwnd, WM_DESTROY, main_OnDestroy); //// TODO: Add window message crackers here... default: return DefWindowProc (hwnd, msg, wParam, lParam); } }
这就是带有三个已准备好工作的消息解析宏的窗口过程!并且,还有一个 TODO 注释提醒你必须在那里添加新的消息解析宏。记住,当你想要将 HANDLE_MSG
宏添加到窗口过程时,请取消选择“Make Window Procedure”(创建窗口过程)复选框。
但是上面的代码什么也没做,因为我们需要处理我们想要的那三个消息的函数。只需返回 Message Cracker Wizard 工具,然后单击“Copy Function”(复制函数)按钮。切换到你的源代码,将光标定位在你想要插入函数体的位置,然后用 Ctrl+V 或 Edit/Paste 菜单粘贴。向导会自动创建带有 **main** 窗口 ID 和 WINDOWSX.H 头文件宏所需的正确参数的函数。
// // Process WM_CLOSE message for window/dialog: main // void main_OnClose(HWND hwnd) { // TODO: Add your message processing code here... } // // Process WM_CREATE message for window/dialog: main // BOOL main_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { // TODO: Add your message processing code here... } // // Process WM_DESTROY message for window/dialog: main // void main_OnDestroy(HWND hwnd) { // TODO: Add your message processing code here... }
向导还会自动创建一个标题注释和一个 TODO 行,提醒你添加代码。现在你可以轻松添加消息处理和处理逻辑,并编写复杂的窗口过程。你可以通过主窗口上的两个复选框删除注释。
更多消息解析器向导功能
程序还有一些其他功能,都相当直观。
消息过滤
这是程序一些用户提出的建议,并已实现。单击“Filters..”(过滤器..)按钮(或按 Ctrl+L),就会出现以下对话框。在那里,你可以选择哪些消息显示在列表框中,并按类型分类(此分类标准来自 Microsoft Spy++ 工具)。
请注意,v2.0 中使用消息过滤对话框的一个当前问题是,当你单击 OK 时,列表框会再次填充,因此之前的选择会丢失(但这并不意味着你之前选择的消息出现在目标代码窗口中会消失)。
紧凑窗口模式
你可能希望减小 Message Cracker Wizard 的窗口大小。这可以通过禁用 **View**(视图)菜单中的“Show Target Code”(显示目标代码)选项(或按 Ctrl+F11)来实现。主窗口将不带目标代码区域显示。
窗口透明度、排除注释和保持在顶层
对于低分辨率显示器或混乱的桌面,另一个可能很有用的功能是窗口透明度功能。单击 **View**(视图)菜单,然后单击 Window Transparency(窗口透明度)菜单,并选择一个透明度百分比(Solid 是 100% 不透明,75% 是 25% 不透明)。*此功能仅适用于 Windows 2000/XP 和 Server 2003 用户。在 9X 操作系统上,只有“Solid”选项可用*。
Exclude Comments(排除注释)功能允许代码生成器排除注释,无论是标题注释还是“TODO”风格的注释。只需在主窗口上选择或取消选择复选框即可。
最后,**Stay On Top**(保持在顶层)功能非常自明。
计划中的功能
以下功能可能会在后续版本中出现:
- 帮助文件。
- 每个消息解析器参数和消息的集成帮助。
- WTL 支持! :)
- 窗口 ID 和设置保存(这可能在下一个 2.x 版本中实现)。
- 每个项目设置和“消息映射”(类似于 MFC)(这也可能在稍后的 2.5 版本中实现)。
玩得开心,编程愉快!
我希望这个小工具对任何 Windows SDK 程序员都有用,当然,也可能是一种编写更干净的 Win32 API 程序的方法。我愿意接受改进工具的建议。如果你觉得这个程序有用,请给我发邮件,因为我将非常高兴听到任何好评。
- 1.0
- 首次发布,2003 年 9 月。
- 1.2
- 已添加**多选**功能!!
- 添加了
WM_COPYDATA
和WM_HOTKEY
消息的丢失解析器。 - 修复了小的界面 bug。
- 2.0
- 添加了**消息过滤**。
- 添加了窗口透明度选项(仅适用于 Windows 2000/XP/Server 2003)。
- 添加了显示/隐藏目标代码。
- 添加了启用/禁用窗口置顶。
- 添加了
WM_CTLCOLORxxxx
消息支持。 - 在列表框中添加了消息类型位图。
- 添加了包含/排除注释选项。
- 修复了键盘逻辑。
- 2.1
- 修复了剪贴板复制 bug(感谢 Agnel Kurian)。
- 该程序现已根据 GPL 许可。
- 2.2
- C++ 代码现代化,接近现代标准
- 已更新以使用 VS2015 编译
- 所有字符串更改为 Unicode
- 修复了分析、警告及相关问题
- 更改为 MIT 许可证