Creating a Window Wrapper Class: Redux

4 minute read

Okay, so I know you’re probably here because you searched for “window wrapper class” or something similar and expected the article that I use to host on Scriptionary that threw a bunch of C++ code at you for you to copy and paste. I regret to inform you that the article you were looking for has ceased to exist.

Sorry about that.

However, in its place I have for you this very post which will teach you how to accomplish creating such a wrapper all by yourself. I hope that is okay since all you really need to know is the how to create a window procedure that you can use with your custom class.

The Window Procedure

The window procedure is a function that is tied to a window upon creation. This function handles the window’s events (also: messages, notifications) such as WM_PAINT, WM_SIZE and a plethora of others that a window can throw at you. The trick is to pass a function callback to WNDCLASS or WNDCLASSEX that is part of your class, which is pretty much impossible since you can’t pass instance members by reference. Let’s take a look at a standard windows procedure:

LRESULT CALLBACK WndProc(
  HWND hwnd,
  UINT message,
  WPARAM wParam,
  LPARAM lParam)
{
  switch (message)
  {
  case WM_PAINT:
    // handle event
    return 0;
  /* many more possible events */
  default:
    return DefWindowProc(hwnd, message, wParam, lParam);
  }
}

Well, that’s pretty much as plain as Windows functionality gets, so nothing too special there. Let’s make this function a member of our imaginary class Window since no matter what, we will need a regular WndProc that will handle our events:

class Window {
  // *snip*
  LRESULT CALLBACK LocalWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
  );
};

Since we can’t pass member functions to serve as callbacks, we’ll have to do it the C+ way (somewhere between C and C++). For this we’ll need the feared static keyword, which signifies that the function is shared by all of the instances of our imaginary Window class:

class Window {
  // *snip*
  static LRESULT CALLBACK StaticWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
  );
};

You may not realize it, but we’ve already defined the hardest part of the entire window wrapper. Since StaticWndProc is static in nature, we can pass it as a function callback since it’s not bound to a specific instance of our Window class — almost like a global function. User Data

You may wonder: Okay, so how do I make this static function specific to my current instance? And most importantly, how do I get from the StaticWndProc back to the regular LocalWdnProc?

Well, this is where we’re lucky that the Windows API provides us with a bit of leeway. Let’s take a look at the CreateWindow function prototype straight from the MSDN documentation:

HWND WINAPI CreateWindow(
  __in_opt  LPCTSTR lpClassName,
  __in_opt  LPCTSTR lpWindowName,
  __in      DWORD dwStyle,
  __in      int x,
  __in      int y,
  __in      int nWidth,
  __in      int nHeight,
  __in_opt  HWND hWndParent,
  __in_opt  HMENU hMenu,
  __in_opt  HINSTANCE hInstance,
  __in_opt  LPVOID lpParam
);

The last parameter, lpParam, allows us to pass custom data to the window procedure which is accessible when the WM_NCCREATE event is active. This means that when we pass static_cast<void*>(this) into lpParam, we can pick that data back up in our static window procedure.

The problem is that this data doesn’t persist beyond the WM_NCCREATE event, so we need to find a way to store it somewhere permanent; this is where that leeway that I mentioned before comes in.

If you’ve never heard of the constant GWLP_USERDATA I don’t blame you since there’s not much documentation about at MSDN. GWLP_USERDATA is a constant passed to the SetWindowLongPtr and GetWindowLongPtr functions, providing access to a pointer-sized chunk of memory attached to your window.

So when we process WM_NCCREATE, we’ll grab the lpParam pointer passed to CreateWindow, and set it as user defined window data by using SetWindowLongPtr with GWLP_USERDATA:

LRESULT Window::StaticWndProc(
  HWND hwnd,
  UINT message,
  WPARAM wParam,
  LPARAM lParam)
{
  // Check if we're currently creating the window:
  if (message == WM_NCCREATE)
    SetWindowLongPtr(
      hwnd,
      GWLP_USERDATA,
      reinterpret_cast<LONG_PTR>(
        reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams
      )
    );

  // Retrieve the data stored in the WM_NCCREATE event and
  // cast it to a Window pointer:
  Window* UserWindow = reinterpret_cast<Window*>(
    GetWindowLongPtr(hwnd, GWLP_USERDATA)
  );

  // If the data returned was NULL, default to the by windows
  // provided default window procedure:
  if (NULL == UserWindow) {
    return DefWindowProc(hwnd, message, wParam, lParam);
  } else {
    return UserWindow->LocalCallback(hwnd, message, wParam, lParam);
  }
}

Conclusion

Let’s review the entire process step by step:

  • Pass the static callback into the WndProc member of the WNDCLASS structure for your window,
  • Pass a this pointer into the lpParam parameter of the CreateWindow function,
  • Retrieve the pointer when handling the WM_NCCREATE event and store it with SetWindowLongPtr using the GWLP_USERDATA constant,
  • After that, retrieve the pointer to your window using GetWindowLongPtr using the GWLP_USERDATA, cast it back to your window class and call the local window procedure.