WndProc的类方法

Class method for WndProc

本文关键字:类方法 WndProc      更新时间:2023-10-16

本文出色地解释了调用类成员WndProc的选项。我在stackoverflow中看到了这个响应,但在CreateWindow之后关联类成员WndProc的主要问题是,正如前面提到的文章中所解释的,一些消息(包括重要的WM_CREATE)将丢失。

我的问题:我想听听专家的意见,看看下面公开的方法或新方法中哪一个是创建类成员WndProc的最佳方法(性能、可维护性…)。

简要介绍了文章中公开的两个最终解决方案(假设它存在一个带有WndProc方法的Window类):

  1. 具有this全局指针存储的每个窗口数据,使用CRITICAL_SECTION对其进行保护,使其线程安全(从此处提取):

    // The helper window procedure
    // It is called by Windows, and thus it's a non-member function
    // This message handler will only be called after successful SetWindowLong call
    // We can assume that pointer returned by GetWindowLong is valid
    // It will route messages to our member message handler
    LRESULT CALLBACK WndProc2(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    // Get a window pointer associated with this window
    Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA);
    // It should be valid, assert so
    _ASSERT(w);
    // Redirect messages to the window procedure of the associated window
    return w->WndProc(hwnd, msg, wp, lp);
    }
    // The temporary global this pointer
    // It will be used only between CreateWindow is called and the first message is processed by WndProc
    // WARNING: it is not thread-safe.
    Window *g_pWindow;
    // Critical section protecting the global Window pointer
    CRITICAL_SECTION g_WindowCS;
    // The helper window procedure
    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
    // Stash global Window pointer into per-window data area
    SetWindowLong(hwnd, GWL_USERDATA, (long) g_pWindow);
    // Unlock global critical section
    g_pWindow->HaveCSLock = false;
    LeaveCriticalSection(&g_WindowCS);
    // Reset the window message handler
    SetWindowLong(hwnd, GWL_WNDPROC, (long) WndProc2);
    // Dispatch first message to the member message handler
    return WndProc2(hwnd, msg, wp, lp);
    }
    

    现在我们可以创建窗口:

    InitializeCriticalSection(&g_WindowCS);
    // Enter the critical section before you write to protected data
    EnterCriticalSection(&g_WindowCS);
    // Set global Window pointer to our Window instance
    // Moved the assignment here, where we have exclusive access to the pointer
    g_pWindow = &w;
    // Set a flag indicating that the window has the critical section lock
    // Note: this must be executed after the above assignment
    g_pWindow->HaveCSLock = true;
    // Create window
    // Note: lpParam is not used
    HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, 0);
    // Leave critical section if window creation failed and our window procedure hasn't released it
    if (g_pWindow->HaveCSLock)
    LeaveCriticalSection(&g_WindowCS);
    // Destroy critical section
    // In production code, you'd do this when application terminates, not immediately after CreateWindow call
    DeleteCriticalSection(&g_WindowCS);
    
  2. 使用CBT挂钩程序(从这里提取):

    // The helper window procedure
    // It is called by Windows, and thus it's a non-member function
    // This message handler will only be called after successful SetWindowLong call from the hook
    // We can assume that pointer returned by GetWindowLong is valid
    // It will route messages to our member message handler
    LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
    {
    // Get a window pointer associated with this window
    Window *w = (Window *) GetWindowLong(hwnd, GWL_USERDATA);
    // It should be valid, assert so
    _ASSERT(w);
    // Redirect messages to the window procedure of the associated window
    return w->WndProc(hwnd, msg, wp, lp);
    }
    // The CBT hook procedure
    // It is called during CreateWindow call before WndProc receives any messages
    // Its job is to set per-window Window pointer to the one passed through lpParam to CreateWindow
    LRESULT CALLBACK CBTProc(int code, WPARAM wp, LPARAM lp)
    {
    if (code != HCBT_CREATEWND) {
    // Ignore everything but create window requests
    // Note: generally, HCBT_CREATEWND is the only notification we will get,
    // assuming the thread is hooked only for the duration of CreateWindow call.
    // However, we may receive other notifications, in which case they will not be passed to other CBT hooks.
    return 0;
    }
    // Grab a pointer passed to CreateWindow as lpParam
    std::pair<Window *, HHOOK> *p = (std::pair<Window *, HHOOK> *) LPCBT_CREATEWND(lp)->lpcs->lpCreateParams;
    // Only handle this window if it wasn't handled before, to prevent rehooking windows when CreateWindow is called recursively
    // ie, when you create windows from a WM_CREATE handler
    if (p->first) {
    // Stash the associated Window pointer, which is the first member of the pair, into per-window data area
    SetWindowLong((HWND) wp, GWL_USERDATA, (long) p->first);
    // Mark this window as handled
    p->first = 0;
    }
    // Call the next hook in chain, using the second member of the pair
    return CallNextHookEx(p->second, code, wp, lp);
    }
    

    现在我们可以创建窗口:

    // Install the CBT hook
    // Note: hook the thread immediately before, and unhook it immediately after CreateWindow call.
    // The hook procedure can only process window creation nofitications, and it shouldn't be called for other types of notifications
    // Additionally, calling hook for other events is wasteful since it won't do anything useful anyway
    HHOOK hook = SetWindowsHookEx(WH_CBT, CBTProc, 0, GetCurrentThreadId());
    _ASSERT(hook);
    // Create window
    // Pass a pair consisting of window object pointer and hook as lpParam
    std::pair<Window *, HHOOK> p(&w, hook);
    HWND hwnd = CreateWindow(TEXT("BaseWnd"), TEXT("Hello, World!"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hinst, &p);
    // Unhook first
    UnhookWindowsHookEx(hook);
    

我个人不会使用这两种方法。全局变量方法有效,但感觉很肮脏。尤其是锁。而CBT的钩子,远远超过了顶部。尽管它指向正确的方向。

在创建过程中将状态信息传递给窗口过程的标准方法是通过CreateWindowCreateWindowExlpParam参数。因此,技术如下:

  1. CreateWindowCreateWindowExlpParam参数中传递实例指针
  2. WM_NCCREATE处理程序中读取此值。该消息将信息作为CREATESTRUCT结构的一部分提供
  3. 仍然在WM_NCCREATE中调用SetWindowLongPtr以将窗口的用户数据设置为实例指针
  4. 所有未来对窗口过程的调用现在都可以通过调用GetWindowLongPtr来获得实例指针

Raymond Chen在这里说明了详细信息:如何使WNDPROC或DLGPROC成为C++类的成员?