A C++ Abstraction for a Window

Here is how to structure your C++ code to enable use of the C++ class abstraction for a window and call the C API from your object-oriented C++ code.

Prerequisites | A Window | Win32 API

The C++ Class

At the outset we write up an abstraction for a window. The point to note here is the static member function Win32WndProc, declared CALLBACK (a macro definition for the Microsoft-specific modifier __stdcall defined in minwindef.h) that establishes the calling convention used for Win32 API functions. If the WndProc was a C++ member function, it would have been passed the implicit this pointer triggering a static error. So the idea is to have Win32WndProc delegate to our C++ WndProc. In this way, we can go on to derive classes from KWindow and write our own WndProc's standing on C++ land.

class KWindow
{
public:
    KWindow();
    virtual ~KWindow();
    void Register(LPCTSTR lpClassName, HINSTANCE hInst);
    virtual void CreateEx(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu);
    virtual WPARAM MsgLoop();
    void Show(int nCmdShow) const;
    void Update() const;
    HWND hWnd() const;

protected:
    virtual void getWindowClass(WNDCLASSEX& wc);
    virtual LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    static LRESULT CALLBACK Win32WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    virtual void onDraw(HDC hDC);
    virtual void onKeyDown(WPARAM wParam, LPARAM lParam);

    HWND m_hWnd;
};

The Mechanism to Delegate to a C++ WndProc

Let us take a look at the code for the two functions

virtual LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK Win32WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

The first one, WndProc, is simply the window procedure (message handler) that we always write for a Win32 code. The other, Win32WndProc is defined in this way

LRESULT CALLBACK KWindow::Win32WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    KWindow* pWindow = NULL;

    if (uMsg == WM_NCCREATE)
    {
        MDICREATESTRUCT* pMDIC = (MDICREATESTRUCT*)((LPCREATESTRUCT)lParam)->lpCreateParams;
        pWindow = (KWindow*)(pMDIC->lParam);
        SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pWindow);
        return TRUE;
    }
    else
    {
        pWindow = (KWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    }

    if (pWindow)
    {
        return pWindow->WndProc(hWnd, uMsg, wParam, lParam);
    }
    else
    {
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

If you look closer, this too is a message handler like WndProc. It handles the WM_NCCREATE message, otherwise delegating to our custom C++ WndProc or DefWindowProc. The message it handles is one of the first few sent by Window (the OS) to your Win32 message handler and therefore is a useful point in time (of a Win32 applications life) for initializations. Here we extract a pointer to our C++ class KWindow* pWindow from lParam, and call SetWindowLongPtr to place it in memory associated with a Win32 window that lives for the window's lifetime. The next time around, we always enter the else block of that first if-else block and extract the pointer using GetWindowLongPtr and use it to delegate to our C++ WndProc. Win32WndProc is the function pointer assigned to WNDCLASSEX. So now we have the mechanism to step into C++ land, after making a start in the C Win32 API space.

One last thing to note is we pass the this pointer from KWindow's member function CreateEx to the Win32 function CreateWindowEx in its last parameter. This is why Win32WndProc finds it in its lParam.

Here is our CreateEx delegating to CreateWindowEx

void KWindow::CreateEx(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu)
{
    HINSTANCE hInst = NULL;

    if (!(hInst = GetModuleHandle(NULL)))
    {
        throw std::runtime_error{ Win32Error(__FILE__, __LINE__, __func__, "GetModuleHandle") };
    }

    RECT rect{0, 0, nWidth, nHeight};
    if (!AdjustWindowRectExForDpi(&rect, dwStyle, FALSE, dwExStyle, GetDpiForSystem()))
    {
        throw std::runtime_error{ Win32Error(__FILE__, __LINE__, __func__, "AdjustWindowRectExForDpi") };
    }

    Register(lpClassName, hInst);

    MDICREATESTRUCT mdic{};
    mdic.lParam = (LPARAM)this;

    if (!(m_hWnd = CreateWindowEx(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, rect.right, rect.bottom, hWndParent, hMenu, hInst, &mdic)))
    {
        throw std::runtime_error{ Win32Error(__FILE__, __LINE__, __func__, "CreateWindowEx") };
    }
}

Other Class Members

The rest of the member functions do little beyond calling the Win32 API to perform the necessary dance to get a Windows program going. The only data it stores is a HWND. There are some message handlers, onDraw and onKeyDown called on WM_KEYDOWN and WM_PAINT, and so on.

Note You should never do the window creation routine (call CreateEx) inside the constructor. If you derive a class from KWindow (say, DerivedKWindow), during its instantiation, the base class constructor will get called and the Win32 API universe will be born with the this pointer pointing to a KWindow instance in memory. In the first few moments, your message handler will be KWindow's WndProc, and then when your code completes constructing the derived class and replaces the this placeholder with a pointer to your derived class instance, the code will switch to calling the derived WndProc as you had desired. However, this switch in the thick of things mean, some of the initial messages will get handled by KWindow::WndProc and after a point, DerivedKWindow::WndProc will be called to handle them. This has the effect that your DerivedKWindow::WndProc will never receive the WM_CREATE message and any initialization you intend to do on WM_CREATE, will never happen.

The Program Entry Point

That is why our wWinMain is the way it is: short.

int WINAPI wWinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInst, _In_ PWSTR lpCmd, _In_ int nShow)
{
    try
    {
        auto win = make_unique<KWindowTest>();
        win->CreateEx(0, _T("KWindowTest"), _T("Test Window"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL);
        win->Show(nShow);
        win->Update();
        return win->MsgLoop();
    }
    catch (std::exception e)
    {
        return 0;
    }
}

Exception Handling

KWindow has rudimentary exception handling facility that relies on a call to the Win32 API GetLastError.

References

  1. __stdcall
  2. WM_NCCREATE
  3. CreateWindowEx