Cool method for WndProc

This article perfectly explains the options for calling a member of the WndProc class. I have seen https://stackoverflow.com/a/167189/... but the main problem of associating a member of the WndProc class after CreateWindow is that some messages will be lost (including the important WM_CREATE), as described in the above article .

My question is . I would like to hear an expert opinion on which of the methods presented below or new is the best (performance, maintanability, ...) for creating a class member of WndProc.

A briefing of the two final solutions presented in the article (suppose there is a Window class with the WndProc method):

  • Data in a window with global pointers repository this, protecting it with CRITICAL_SECTION, to make it thread safe (extracted from here ):

    // The helper window procedure
    // It is called by Windows, and thus it 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);
    }
    

    Now we can create a window:

    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);
    
  • Using the CBT hook procedure (extracted from here ):

    // The helper window procedure
    // It is called by Windows, and thus it 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);
    }
    

    Now we can create a window:

    // 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);
    
+3
source share
1 answer

I personally would not use any of these methods. The global variable approach works, but feels dirty. Especially with the castle. And the CBT hook as well as the top. Although he points in the right direction.

The standard way to transfer state information to your window procedure during creation is done using the lpParam CreateWindowor parameter CreateWindowEx. Thus, the method is as follows:

  • lpParam CreateWindow CreateWindowEx.
  • WM_NCCREATE. CREATESTRUCT.
  • WM_NCCREATE SetWindowLongPtr, .
  • , GetWindowLongPtr.

: WNDPROC DLGPROC ++?

+5

All Articles