October 14th, 2019

A common mistake when you try to create a C++ class that wraps a window procedure: Saving the window handle too late

A common mistake when you try to create a C++ class that wraps a window procedure is saving the window handle too late.

// Code in italics is wrong.
class MyWindowClass
{
private:
 HWND m_hwnd = nullptr;

 static LRESULT CALLBACK StaticWndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
  MyWindowClass *self;
  if (uMsg == WM_NCCREATE) {
   LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
   self = static_cast<MyWindowClass*>(lpcs->lpCreateParams);
   SetWindowLongPtr(hwnd, GWLP_USERDATA,
                    reinterpret_cast<LONG_PTR>(self));
  } else {
   self = reinterpret_cast<MyWindowClass*>(
               GetWindowLongPtr(hwnd, GWLP_USERDATA));
  }
  if (self) {
   return self->WndProc(uMsg, wParam, lParam);
  }
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
 }

 LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
 {
   switch (uMsg) {
   ...
   default:
    return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
   }
 }

public:
 void CreateTheWindow()
 {
   ... RegisterClass() ...
   m_hwnd = CreateWindowEx(..., this);
 }
};

This code follows the usual pattern for a window procedure wrapper: The this pointer is passed as the creation parameter, and the WM_NC­CREATE message handler stashes the creation parameter in the window extra bytes, thereby allowing the this pointer to be recovered from the window handle when handling future messages.

However, there’s a problem with the above code. Can you spot it?

The problem is that it sets the m_hwnd member variable too late.

As written, the code doesn’t set the m_hwnd member variable until the Create­Window­Ex function returns. But creating a window involves sending many messages.

For every message received during window creation, The WndProc member function runs with a null m_hwnd. This means that when it calls Def­Window­Proc(m_hwnd, ...), it’s passing an invalid parameter.

Many of the messages sent during window creation are kind of important to pass through to Def­Window­Proc. For example, if you neglect to pass WM_NC­CREATE to Def­Window­Proc, your window will not be properly initialized.

The solution is to set m_hwnd as soon as you learn what the window handle is.

  if (uMsg == WM_NCCREATE) {
   LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
   self = static_cast<MyWindowClass*>(lpcs->lpCreateParams);
   self->m_hwnd = hwnd; // save the window handle too!
   SetWindowLongPtr(hwnd, GWLP_USERDATA,
                    reinterpret_cast<LONG_PTR>(self));
  } 

Don’t wait for Create­Window­Ex to return. By then, it’s too late.

 

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

18 comments

Discussion is closed. Login to edit/delete existing comments.

  • Georg Rottensteiner

    Everytime I see this:

    default:
    return DefWindowProc(m_hwnd, uMsg, wParam, lParam);

    it irks me tremendously. This is also used in several WndProc samples.
    While itself it’s correct, as soon as some one handles a message but also needs to call DefWindowProc he has to sprinkle the DefWindowProc call everywhere. Or more common, forgets to pass the message on at all.

    It’s basically a sample induced bug waiting to happen.

  • Alex Cohn

    This is a good example for similar situations, but for this specific wrapper, the safe solution could be to change the signature of WndProc and always pass hwnd to it.

    • Antonio Rodríguez

      It wouldn't solve anything. You are just shifting the blame. StaticWndProc() must obtain the handle from somewhere in order to pass it to WndProc, and you can't obtain it from thin air, since StaticWndProc() is a static method (it has to be, because it has to fit the standard window procedure definition), and has to work with the hwnd provided by the window manager, without knowing beforehand if it's registered or to which object instance...

      Read more
      • Ben Voigt

        StaticWndProc() doesn’t need to go looking for it, since it received the HWND in its first parameter.

      • Antonio Rodríguez

        The problem is still the same. If you are building a class, you surely will have more methods than just WndProc() and StaticWndProc(). In fact, the sample code shows at least one: CreateTheWindow(). If you wanted to call WndProc() without having an instance pointer (this or self), WndProc() itself would have to be static, and it could not call any of the non-static methods, at least not directly. That's why the implementation takes the...

        Read more
  • Antonio Rodríguez

    Is WM_NC­CREATE guaranteed by contract to be the first message sent to a window? Its documentation explicitly says it's send before WM_CREATE, but CreateWindow() documentation also mentions other messages, such as WM_GETMINMAXINFO. I think it's reasonable to expect the creation messages (WM_NC­CREATE and WM_CREATE) to be the very first sent, but when several messages are sent as part of one operation, expecting them in a determinate order has caused compatibility problems in the past, so...

    Read more
    • Paul Topping

      It would be good defensive programming to initialize m_hwnd to null in the constructor and then test m_hwnd in any message handler before using it.

      • Me Gusta

        It would be much better for performance to make it contractual that m_hwnd must be a valid window handle by the time WM_NCCREATE has finished processing. This means that you could basically assert on it rather than having a branch instruction at the start of every handler.
        Assuming that you do proper testing with the debug version of the application, you can assume that the handle is always valid and the assert will compile away...

        Read more
    • Michael Liu

      As I recall, WM_GETMINMAXINFO is sent before WM_NCCREATE. Raymond’s code doesn’t seem to forward that initial WM_GETMINMAXINFO to MyWindowClass::WndProc.

      • Me Gusta

        It's not as if you can save it at any point before then without using external resources.
        The only way you know how to get the window handle into the class without using external resource is by knowing what class you are talking about. Since this is passed into the window via CREATESTRUCT, the very first time you get to see CREATESTRUCT is in WM_NCCREATE.
        I'm sure that if you try very hard, you could...

        Read more
      • Tim Weis

        I will object to the idea, that handling WM_GETMINMAXINFO were a rare occurrence. Especially for dialogs it is very common to want to set a minimum tracking size.

      • Ryan P

        Another trick is to use a dynamically allocated thunk for the StaticWndProc, and tuck away the class ‘this’ pointer inside the thunk itself before CreateWindow/Ex() is even called. The thunk can access its ‘this’ pointer directly and forward all messages directly to the class WndProc, without relying on CREATESTRUCT or GWLP_USERDATA at all. This is the technique that Borland’s VCL uses to link its classes to the HWNDs they create.

      • Tim Weis

        That's not correct. You can register a (thread-local) CBT hook, and get notified about window creation before any messages are being sent out. Since window creation is strictly single-threaded, you can safely transfer a class instance pointer using thread local storage. The CBT hook procedure also has access to the HWND, so you can tuck the instance pointer away right there.

        MFC uses this technique to establish the relationship between its C++ class instances and the...

        Read more
      • Me Gusta

        I was unsure of the first line, thank you for showing me that I was right to be unsure. I will edit that post to better reflect what I mean.
        By the way, if you read the whole post and not the first line then you would see that I talk about devising ways to get the pointer to the class into the static window procedure sooner. So I did cover things like this.

      • anonymous

        This comment has been deleted.

      • Antonio Rodríguez

        Strange… Notifying a window of its placement and requesting modifications to it does not seem a sensible move before the window itself is created. But nobody said message ordering had to be intuitive, and Raymond has shown us that sometimes it isn’t.

      • cricwood@nerdshack.com

        WM_GETMINMAXINFO queries the constraints on window size. If you asked after WM_NCCREATE, your window could initially be created with the wrong size! Asking beforehand sure seems sensible to me.

      • Antonio Rodríguez

        Perhaps. But one could also argue that querying for window size/position just after the call to CreateWindow() or CreaateWindowEx(), which lets you explicitly provide that information, is, at the very least, redundant.