This article explains brilliantly the options to call a class member WndProc. I've seen this response in stackoverflow but the main problem associating class member WndProc after CreateWindow is that some messages will be lost (including the important WM_CREATE) as explained in the mentioned article.
My question: I would like to hear the opinion from an expert on which of the methods exposed below or new one is the best one (performance, maintanability, ...) to create a class member WndProc.
Briefing the two final solutions exposed in the article (suposing that it exists a Window class with WndProc method):
Per-window data with
this
global pointer storage, 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'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); }
And now we can create the 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 CBT hook procedure (extracted from 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); }
And now we can create the 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);