WndProc的类方法

时间:2014-02-12 11:44:10

标签: c++ winapi wndproc

这个article精辟地解释了调用类成员WndProc的选项。我见过this response in stackoverflow但是在CreateWindow之后关联类成员WndProc的主要问题是某些消息将丢失(包括重要的WM_CREATE),如the mentioned article中所述。

我的问题:我想听听专家关于下面公开的方法或新方法是创建课程的最佳方法(性能,维护性......)的意见成员WndProc。

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

  1. 具有this全局指针存储的每个窗口数据,使用CRITICAL_SECTION保护它以使其线程安全(从here中提取):

    // 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钩子程序(摘自here):

    // 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);
    

1 个答案:

答案 0 :(得分:5)

我个人不会使用这两种方法。全局变量方法有效,但感觉很脏。特别是带锁。 CBT挂钩远远超过顶部。虽然它指向了正确的方向。

在创建过程中将状态信息传递给窗口过程的标准方法是lpParam CreateWindowCreateWindowEx的{​​{1}}参数。所以技术如下:

  1. 将您的实例指针传递到lpParamCreateWindow的{​​{1}}参数。
  2. CreateWindowEx处理程序中读取此值。该消息提供的信息是WM_NCCREATE结构的一部分。
  3. 仍在CREATESTRUCT调用WM_NCCREATE,将窗口的用户数据设置为实例指针。
  4. 以后对窗口过程的所有调用现在都可以通过调用SetWindowLongPtr来获取实例指针。
  5. Raymond Chen在此详细说明了这些细节:How can I make a WNDPROC or DLGPROC a member of my C++ class?