[转]使用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)。
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 宏的定义如下所示:
#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 中定义的宏,其原型如下:
#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 的地方,其上面有一行注释:
/* 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更名为自己选择的函数名称即可。
 
原文地址:https://www.cnblogs.com/wxxweb/p/2093426.html