[转]使用HANDLE_MSG宏简化Win32应用的开发
Win32应用中的回调函数 WndProc 用于接收 Windows 向应用程序直接发送的消息,以及响应消息。大多情况下,我们这样编写代码:
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { int cxClient, cyClient; PAINTSTRUCT ps; HDC hdc; switch ( message ) { case WM_SIZE: cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); break ; case WM_PAINT: hdc = BeginPaint( hWnd, &ps ); EndPaint( hWnd, &ps ); break ; case WM_DESTROY: PostQuitMessage( 0 ); break ; } return DefWindowProc( hWnd, message, wParam, lParam ); } |
这种方式通过switch分支语句分理处理各个消息,结构简单明了。但有2个问题:
1.在所处理的消息多了后,全部代码都集中于一个switch语句中,变量容易相互污染,且代码很长,不容易定位代码。
2.大多数的消息都有相应的 WPARAM、LPARAM 参数,但对于每个消息,其 WPARAM、LPARAM 参数的具体内容又不一致。就像上面 WM_SIZE 中的代码,我们怎么知道 cyClient、cyClient 的信息就放在 lParam 而不是 wParam 中,且 cxClient 的信息在 lParam 的低位,cyClient 的信息在 lParam 的高位?我们需要随时查询 SDK 文档以了解这两个参数中各有什么含义,以及如何恰当地将这些参数抽取出来。不管怎样,上面的代码实际上就是个让人一团雾水的魔术代码(Magic Code)。
1.在所处理的消息多了后,全部代码都集中于一个switch语句中,变量容易相互污染,且代码很长,不容易定位代码。
2.大多数的消息都有相应的 WPARAM、LPARAM 参数,但对于每个消息,其 WPARAM、LPARAM 参数的具体内容又不一致。就像上面 WM_SIZE 中的代码,我们怎么知道 cyClient、cyClient 的信息就放在 lParam 而不是 wParam 中,且 cxClient 的信息在 lParam 的低位,cyClient 的信息在 lParam 的高位?我们需要随时查询 SDK 文档以了解这两个参数中各有什么含义,以及如何恰当地将这些参数抽取出来。不管怎样,上面的代码实际上就是个让人一团雾水的魔术代码(Magic Code)。
WindowsX.h 定义了许多宏,可以帮助我们解决上述这些问题。其中 HANDLE_MSG 宏可以大大简化 Win32 开发。下面我们先看使用 HANDLE_MSG 宏后的代码:
// 处理 WM_SIZE 消息. void OnSize( HWND hwnd, UINT state, int cx, int cy) { } // 处理 WM_PAINT 消息. void OnPaint( HWND hWnd) { PAINTSTRUCT ps; HDC hdc; hdc = BeginPaint( hWnd, &ps ); EndPaint( hWnd, &ps ); } // 处理 WM_DESTROY 消息. void OnWinDestroy( HWND hWnd) { PostQuitMessage(0); } // 主窗口过程. LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch (message) { HANDLE_MSG(hWnd, WM_SIZE, OnSize); HANDLE_MSG(hWnd, WM_PAINT, OnPaint); HANDLE_MSG(hWnd, WM_DESTROY, OnWinDestroy); } return DefWindowProc(hWnd, message, wParam, lParam); } |
与使用 HANDLE_MSG 宏之前的代码相比,我们根本不需使用 LOWORD、HIWORD 来提取 cxCleint 及 cyClient 的信息,OnSize 函数的参数已经有 cx 及 cy,而且还传进一个表示窗口变化类型的参数 state,如 SIZE_RESTORED, SIZE_MAXSHOW 等。ps 与 hdc 这两个变量移至 OnPaint 函数中而成为真正的局域变量。在 switch 分支语句中,对于每一个消息,都使用 HANDLE_MSG 宏清晰地将消息与消息处理函数对应起来,代码非常简洁。而无论是在消息处理函数,或是 switch 分支中,我们均无需显式地加上 break 语句。
下面我们来看看 HANDLE_MSG 宏是如何做到这一点的。HANDLE_MSG 宏的定义如下所示:
下面我们来看看 HANDLE_MSG 宏是如何做到这一点的。HANDLE_MSG 宏的定义如下所示:
#define HANDLE_MSG(hwnd, message, fn) \ case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn)) |
HANDLE_MSG(hwnd, message, fn) 是宏的签名。hwnd 是需处理消息的窗口句柄,message 是消息,fn 是指向负责消息处理的函数指针。当编译器遇到此宏,将在编译时将其转换为:
case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn)) |
的形式。## 是 ANSIC 标准中的预处理器,用于将前后两个符号直接连接起来。因此,当 message 为 WM_SIZE 时,HANDLE_##message 就变成:
HANDLE_WM_SIZE |
因此,对于
HANDLE_MSG(hWnd, WM_SIZE, OnSize) |
编译器将转换为:
case WM_SIZE: return HANDLE_WM_SIZE(hwnd, wParam, lParam, OnSize) |
我们注意到,尽管 HANDLE_MSG 宏中未使用 wParam 及 lParam 参数,但展开宏后,这两个参数均加进来了。
HANDLE_WM_SIZE 也是一个在 WindowsX.h 中定义的宏,其原型如下:
HANDLE_WM_SIZE 也是一个在 WindowsX.h 中定义的宏,其原型如下:
#define HANDLE_WM_SIZE(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), ( UINT )(wParam), ( int )( short )LOWORD(lParam), ( int )( short )HIWORD(lParam)), 0L) |
正是在 HANDLE_WM_SIZE 宏中,自动将 wParam, lParam 的高位及低位分别抽出,作为 OnSize 函数的实参进行传递。
应注意,不同消息处理函数的参数列表是不一样的。如 OnSize 函数,其参数列表包括 HWND, UINT, int, int, 而OnPaint、OnWindDestory 函数均只有一个HWND参数。那么,如何声明这些消息处理函数的签名?很简单,在 WindowsX.h文件中找到定义 HANDLE_WM_SIZE 的地方,其上面有一行注释:
应注意,不同消息处理函数的参数列表是不一样的。如 OnSize 函数,其参数列表包括 HWND, UINT, int, int, 而OnPaint、OnWindDestory 函数均只有一个HWND参数。那么,如何声明这些消息处理函数的签名?很简单,在 WindowsX.h文件中找到定义 HANDLE_WM_SIZE 的地方,其上面有一行注释:
/* void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy) */ #define HANDLE_WM_SIZE(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), ( UINT )(wParam), ( int )( short )LOWORD(lParam), ( int )( short )HIWORD(lParam)), 0L) |
Cls_OnSize 函数已经给出了函数的签名,因此,我们只需将此签名复制过来,并将Cls_OnSize更名为自己选择的函数名称即可。
分类:
Windows SDK
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?