For a current C++ project I was making a class Window cross-platform wrapper, and I ran into a problem I hadn't noticed before.

In Windows you have a WndProc callback function, which is used to pump all the window messages through, for each specific window.

The WndProc declaration is:

LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);

As I was making a class Window I obviously wanted to have a virtual WndProc method, which each instance or class derivation could override.

Since the function declaration explicitly is given to us, we can't change how it works.

We can't use a method due to thiscall.

For the GCC compiler, thiscall is almost identical to cdecl: The caller cleans the stack, and the parameters are passed in right-to-left order. The difference is the addition of the this pointer, which is pushed onto the stack last, as if it were the first parameter in the function prototype.

What this means is that if I add the following to the class Window:

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

Then it will actually be the following, due to the nature of how methods work:

LRESULT CALLBACK WndProc(Window&, HWND, UINT, WPARAM, LPARAM);

Which means that the function declaration aren't the same and thereby not interchangeable!


I made a really simple solution for this, which in short looks like this:

static LRESULT CALLBACK WndProcDefault(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    if (const LONG_PTR user_data = GetWindowLongPtr(hwnd, GWL_USERDATA))
    {
        Window *window = reinterpret_cast<Window*>(user_data);

        return window->WndProc(hwnd, msg, wparam, lparam);
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

class Window
{
    Window(void)
    {
        // ...

        wc.lpfnWndProc = &WndProcDefault;

        // ...

        SetWindowLongPtr(this->hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(this));
    }

    virtual LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
    {
        // ...

        return DefWindowProc(hwnd, msg, wparam, lparam);
    }
};