1 Win32窗口的创建过程

学过Win32界面编程的都知道,创建一个Win32窗口一般经过以下几个步骤:

  • (1)定义窗口对象
  • (2)注册窗口
  • (3)创建窗口
  • (4)显示窗口
  • (5)消息处理函数
  • (6)消息循环

一个简单的Win32窗口创建代码与以下代码类似,

#include <windows.h>

//声明函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);


//主函数WinMain()
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{

    //窗口类名
    TCHAR cls_name[] = TEXT("MyWinClass");

    //设计窗口类
    WNDCLASSEX wce = { 0 };

    wce.cbSize = sizeof(WNDCLASSEX);                //结构体大小     
    wce.style = CS_HREDRAW | CS_VREDRAW;            //大小改变,水平和垂直重绘
    wce.lpfnWndProc = WindowProc;                   //窗口回调函数地址
    wce.cbClsExtra = 0;                             //类的附加数据
    wce.cbWndExtra = 0;                             //窗口的附加数据
    wce.hInstance = hInstance;                      //应用程序实例句柄
    wce.hIcon = LoadIcon(NULL, IDI_APPLICATION);    //大图标
    wce.hIconSm = wce.hIcon;                        //小图标
    wce.hCursor = LoadCursor(NULL, IDC_ARROW);      //光标
    wce.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //背景色
    wce.lpszMenuName = NULL;                        //菜单
    wce.lpszClassName = cls_name;                   //窗口类的名称

    //注册窗口类
    //ATOM nres = RegisterClassEx(&wce);
    if (FALSE == RegisterClassEx(&wce))
        return 1;

    //创建窗口
    HWND hWnd = CreateWindowEx(
        WS_EX_APPWINDOW,        //窗口的扩展样式
        cls_name,               //窗口类名
        TEXT("我的窗口"),       //窗口标题
        WS_OVERLAPPEDWINDOW,    //窗口风格样式
        200, 120, 600, 400,     //窗口x,y,宽度,高度
        NULL,                   //父窗口句柄
        NULL,                   //菜单句柄
        hInstance,              //应用程序实例句柄
        NULL);                  //附加数据

    //回调函数要写上默认的处理DefWindowProc,不然创建失败
    if (!hWnd)
        return 1;

    //显示,更新窗口
    ShowWindow(hWnd, SW_SHOW);
    UpdateWindow(hWnd);

    //添加加速键表
    HACCEL hAccel=NULL;
    /*::LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1);*/

    //消息循环
    MSG msg;
    while (GetMessage(&msg,NULL,0,0))
    {
        if(!TranslateAccelerator(hWnd,hAccel,&msg))//有加速键表时用这,没有就不用
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

    }


    return 0;
}

//窗口消息处理函数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:    break;
    }

    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

一般情况下我们都会使用一个静态函数作为窗口的消息处理函数,就如同上述代码所示。但是在某些特殊情况下我们需要将某个自定义窗口的消息处理函数不采用静态函数而采用某个类的成员函数,方便封装,这种情况下有没有什么骇客的方式可以实现呢?

2 使用类的成员函数作为Win32窗口消息处理函数WindowProc

2.1 实现方式

在本文,将介绍一种特殊的方式,该方式主要是使用了Delphi的MakeObjectInstance的思想。

工具WndProcRemapUtil.h的代码如下:

#ifndef WNDPROC_REMAP_UTIL_H_
#define WNDPROC_REMAP_UTIL_H_

#include <windows.h>

const SIZE_T PageSize = 4096;

//产生一个代理函数
template <typename T>
static WNDPROC MakeObjectInstance(LPVOID AObject, T AMethod)
{
    union
    {
        T        MethodAddr;//成员函数指针
        LPVOID   NomralAddr;//正常指针
    }ut;//因为VC不允许成员函数指针转换到普通指针。只能变通的通过union来实现
    const unsigned char BlockCode[] = {
#ifdef _WIN64
    0x55,//{ push rbp }
    0x48, 0x83, 0xEC, 0x40,//{ sub rsp,0x40 }
    0x48, 0x8B, 0xEC,//{ mov rbp,rsp }
    0x48, 0x89, 0x4D, 0x50,//{ mov[rbp + 0x50],rcx }
    0x89, 0x55, 0x58,//{ mov[rbp + 0x58],edx }
    0x4C, 0x89, 0x45, 0x60,//{ mov[rbp + 0x60],r8 }
    0x4C, 0x89, 0x4D, 0x68,//{ mov[rbp + 0x68],r9 }
    0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,//{ mov rcx,AObject }
    0x48, 0x8B, 0x55, 0x50,//{ mov rdx,[rbp + 0x50] }
    0x44, 0x8B, 0x45, 0x58,//{ mov r8,[rbp + 0x58] }
    0x4C, 0x8B, 0x4D, 0x60,//{ mov r9,[rbp + 0x60] }
    0x48, 0x8B, 0x45, 0x68,//{ mov rax,[rbp + 0x68] }
    0x48, 0x89, 0x44, 0x24, 0x20,//{ mov[rsp + 0x20],rax }
    0x49, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,//{ mov r11, AMethod }
    0x49, 0xFF, 0xD3,//{ call r11 }
    0x48, 0x8D, 0x65, 0x40,//{ lea rsp,[rbp + 0x40] }
    0x5D,//{ pop rbp }
    0xC3//{ ret }

#else
    0x58,//{ pop eax }
    0x68, 0x00, 0x00, 0x00, 0x00,//{ push AObject }
    0x50,//{ push eax }
    0xB8, 0x00, 0x00, 0x00, 0x00,//{ mov eax, AMethod }
    0xFF, 0xE0//{ jmp eax }

#endif // endif
    };

    size_t CodeBytes = sizeof(BlockCode);
    LPVOID  Block = VirtualAlloc(nullptr, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(Block, BlockCode, CodeBytes);
    unsigned char* bBlock = (unsigned char*)Block;
    ut.MethodAddr = AMethod;
#ifdef _WIN64
    *PLONG64(&bBlock[25]) = LONG64(AObject);
    *PLONG64(&bBlock[0x38]) = LONG64(ut.NomralAddr);
#else
    *PLONG32(&bBlock[2]) = LONG32(AObject);
    *PLONG32(&bBlock[8]) = LONG32(ut.NomralAddr);
#endif
    return (WNDPROC)Block;
}

// 释放代理函数
static void FreeObjectInstance(WNDPROC wndProc)
{
    VirtualFree(wndProc, PageSize, MEM_RELEASE);
}

#endif // !WNDPROC_REMAP_UTIL_H_

2.2 使用方式

先在类A中声明一个成员函数作为窗口消息处理函数

class A
{
public:
    A()
    {

    }
    ~A()
    {

    }
    LRESULT CALLBACK OpenGLWndDisplayProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC hdc;
        PAINTSTRUCT ps;

        switch (message)
        {
        case WM_CREATE:
        {
            break;
        }


        case WM_SIZE:
        {
            break;
        }

        case WM_PAINT:
        {
            HDC hDC = ::GetDC(hWnd);

            RECT rc = { 0 };

            GetClientRect(hWnd, &rc);

            HBRUSH hbr = ::CreateSolidBrush(RGB(0x00, 0xFF, 0x00)); // 绿色画刷

            ::FillRect(hDC, &rc, hbr);

            ::DeleteObject(hbr);

            ::ReleaseDC(hWnd, hDC);

            break;
        }

        case WM_DESTROY:
        {
            break;
        }

        case WM_LBUTTONDOWN:
        {
            break;
        }

        case WM_LBUTTONUP:
        {
            break;
        }

        case WM_MOUSEMOVE:
        {
            break;
        }

        case WM_RBUTTONDOWN:
        {
            break;
        }

        case WM_RBUTTONUP:
        {
            break;
        }

        case WM_MBUTTONDOWN:
        {
            break;
        }

        case WM_MBUTTONUP:
        {
            break;
        }

        case WM_MOUSEWHEEL:
        {
            break;
        }

        case WM_KEYDOWN:
        {
            break;
        }

        default:
            break;
        }

        return DefWindowProc(hWnd, message, wParam, lParam);
    }
};

然后在创建Win32窗口,注册窗口类时使用以下代码将A类的成员函数OpenGLWndDisplayProc作为窗口消息回调函数,

A a;
WNDPROC wndProc = MakeObjectInstance(&a, &A::OpenGLWndDisplayProc);

//设计窗口类
WNDCLASSEX wce = { 0 };

wce.cbSize = sizeof(WNDCLASSEX);                //结构体大小     
wce.style = CS_HREDRAW | CS_VREDRAW;            //大小改变,水平和垂直重绘
wce.lpfnWndProc = wndProc;                  //窗口回调函数地址
wce.cbClsExtra = 0;                             //类的附加数据
wce.cbWndExtra = 0;                             //窗口的附加数据
wce.hInstance = hInstance;                      //应用程序实例句柄
wce.hIcon = LoadIcon(NULL, IDI_APPLICATION);    //大图标
wce.hIconSm = wce.hIcon;                        //小图标
wce.hCursor = LoadCursor(NULL, IDC_ARROW);      //光标
wce.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //背景色
wce.lpszMenuName = NULL;                        //菜单
wce.lpszClassName = cls_name;                   //窗口类的名称

//注册窗口类
//ATOM nres = RegisterClassEx(&wce);
if (FALSE == RegisterClassEx(&wce))
    return 1;

最后在程序退出时释放

FreeObjectInstance(wndProc);

这样一种骇客的方式可以神奇的将窗口消息回调处理函数映射为类的成员函数,极大地方便了我们的封装和使用。

参考链接