How to Use Wndproc as a Class Function

How to use WndProc as a class function

A non-static class method has a hidden this parameter. That is what prevents the method from being used as a WndProc (or any other API callback). You must declare the class method as static to remove that this parameter. But as you already noticed, you cannot access non-static members from a static method. You need a pointer to the object in order to access them.

In the specific case of a WndProc callback, you can store the object pointer in the HWND itself (using either SetWindowLongPtr(GWLP_USERDATA) or SetProp()), then your static method can retrieve that object pointer from the hWnd parameter (using GetWindowLongPtr(GWLP_USERDATA) or GetProp()) and access non-static members using that object pointer as needed.

For example:

private:
HWND m_Wnd;
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK Client::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
Client *pThis;

if (msg == WM_NCCREATE)
{
pThis = static_cast<Client*>(reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams);

SetLastError(0);
if (!SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis)))
{
if (GetLastError() != 0)
return FALSE;
}
}
else
{
pThis = reinterpret_cast<Client*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
}

if (pThis)
{
// use pThis->member as needed...
}

return DefWindowProc(hwnd, msg, wParam, lParam);
}
m_Wnd = CreateWindowEx(..., this);

WndProc as class method

I got interested in this problem, spent some time debugging it, placing special marker into that CREATESTRUCT and looking for them in the memory window, etc.

Then I got lucky: while I let it run, I noticed that WM_NCCREATE case was entered repeatedly, and taking a closer look I got it:

if (msg = WM_NCCREATE)

That would grab the first message (WM_GETMINMAXINFO), do some inappropriate casting, and so on...

Why can't my WndProc be in a class?

C++ treats member functions and free functions differently - member functions need to have access to a this pointer, and typically that's passed in as a hidden first parameter. Consequently, an n-argument member function would be most similar to an (n+1)-argument free function, which means that code trying to call your WndProc would pass in the wrong number of arguments.

You can, however, declare WndProc as a static member function, which eliminates the this pointer. This code should work:

class Simple
{
public:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
...
}
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR commandLine, int cmdShow)
{
Simple *simple = new Simple();
...

wndClass.lpfnWndProc = simple->WndProc;
...
}

Of course, this means you can't directly access the fields of the class. You could get around this by embedding a pointer to the class into the extra bytes reserved for each window instance, perhaps by using SetWindowLongPtr. Once you've done that, you can recover the receiver object pointer by writing something like this:

class Simple
{
public:
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
Simple* me = reinterpret_cast<Simple*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (me) return me->realWndProc(hwnd, msg, wParam, lParam);
return DefWindowProc(hwnd, msg, wParam, lParam);
}
private:
LRESULT CALLBACK realWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Yay! I'm a member function!
}
};

Hope this helps!

Class method for WndProc

I personally would not use either of these methods. The global variable approach works, but feels dirty. Especially with the lock. And the CBT hook is, well over the top. Although it points in the right direction.

The standard way to pass state information to your window procedure during creation is through lpParam parameter of CreateWindow or CreateWindowEx. So the technique is as follows:

  1. Pass your instance pointer in the lpParam parameter of CreateWindow or CreateWindowEx.
  2. Read this value in your WM_NCCREATE handler. That message supplies the information as part of the CREATESTRUCT struct.
  3. Still in WM_NCCREATE call SetWindowLongPtr to set the user data of the window to the instance pointer.
  4. All future calls to the window procedure can now obtain the instance pointer by calling GetWindowLongPtr.

Raymond Chen illustrates the details here: How can I make a WNDPROC or DLGPROC a member of my C++ class?

Win32 WndProc as class member

Make it static:

static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

How do I `std::bind` a non-static class member to a Win32 callback function `WNDPROC`?

While JohnB already explained the details why this is not possible, here is a common solution to the problem you are trying to solve: Granting class instance access to a static class member.

The guiding principle to the solution is that an instance pointer must be stored in a way that is accessible to the static class member. When dealing with windows the extra window memory is a good place to store this information. The requested space of extra window memory is specified through WNDCLASSEXW::cbWndExtra while data access is provided through SetWindowLongPtr and GetWindowLongPtr.

  1. Store an instance pointer in the window extra data area after construction:

    void Create()
    {
    WNDCLASSEXW WindowClass;
    // ...
    // Assign the static WindowProc
    WindowClass.lpfnWndProc = &MainWindow::StaticWindowProc;
    // Reserve space to store the instance pointer
    WindowClass.cbWndExtra = sizeof(MainWindow*);
    // ...
    RegisterClassExW(&WindowClass);
    m_hWnd = CreateWindowEx( /* ... */ );

    // Store instance pointer
    SetWindowLongPtrW(m_hWnd, 0, reinterpret_cast<LONG_PTR>(this));
    }
  2. Retrieve the instance pointer from the static window procedure and call into the window procedure member function:

    static LRESULT CALLBACK StaticWindowProc( _In_ HWND hwnd,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam )
    {
    // Retrieve instance pointer
    MainWindow* pWnd = reinterpret_cast<MainWindow*>(GetWindowLongPtrW(hwnd, 0));
    if ( pWnd != NULL ) // See Note 1 below
    // Call member function if instance is available
    return pWnd->WindowProc(hwnd, uMsg, wParam, lParam);
    else
    // Otherwise perform default message handling
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    The signature of the class member WindowProc is the same as in the code you provided.

This is one way to implement the desired behavior. Remy Lebeau suggested a variation to this which has the benefit of getting all messages routed through the class member WindowProc:

  1. Allocate space in the window extra data (same as above):

    void Create()
    {
    WNDCLASSEXW WindowClass;
    // ...
    // Assign the static WindowProc
    WindowClass.lpfnWndProc = &MainWindow::StaticWindowProc;
    // Reserve space to store the instance pointer
    WindowClass.cbWndExtra = sizeof(MainWindow*);
    // ...
  2. Pass instance pointer to CreateWindowExW:

        m_hWnd = CreateWindowEx( /* ... */,
    static_cast<LPVOID>(this) );
    // SetWindowLongPtrW is called from the message handler
    }
  3. Extract instance pointer and store it in the window extra data area when the first message (WM_NCCREATE) is sent to the window:

    static LRESULT CALLBACK StaticWindowProc( _In_ HWND hwnd,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam )
    {
    // Store instance pointer while handling the first message
    if ( uMsg == WM_NCCREATE )
    {
    CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam);
    LPVOID pThis = pCS->lpCreateParams;
    SetWindowLongPtrW(hwnd, 0, reinterpret_cast<LONG_PTR>(pThis));
    }

    // At this point the instance pointer will always be available
    MainWindow* pWnd = reinterpret_cast<MainWindow*>(GetWindowLongPtrW(hwnd, 0));
    // see Note 1a below
    return pWnd->WindowProc(hwnd, uMsg, wParam, lParam);
    }

Note 1: The instance pointer is stored into the window extra data area after the window has been created while the lpfnWndProc is set prior to creation. This means that StaticWindowProc will be called while the instance pointer is not yet available. As a consequence the if-statement inside StaticWindowProc is required so that messages during creation (like WM_CREATE) do get properly handled.

Note 1a: The restrictions stated under Note 1 do not apply to the alternative implementation. The instance pointer will be available going forward from the first message and the class member WindowProc will consequently be called for all messages.

Note 2: If you want to destroy the C++ class instance when the underlying HWND is destroyed, WM_NCDESTROY is the place to do so; it is the final message sent to any window.

Use object method as WinApi WndProc callback

The usual is something on this order:

#include <windows.h>

class BaseWindow {

static LRESULT CALLBACK internal_WndProc(HWND hWnd, int msg, WORD wParam, LONG lParam) {
BaseWindow *c = (BaseWindow *)GetWindowLong(hWnd,GWLP_USERDATA);

if (c == NULL)
return DefWindowProc(hWnd, msg, wParam, lParam);

return c->WindowProc(hWnd, msg, wParam, lParam);
}

public:
virtual int WindowProc(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam) = 0;

BaseWindow(HINSTANCE instance) {
WNDCLASS window_class = {0};
HWND window;
HMENU my_menu;

window_class.lpfnWndProc = (WNDPROC)internal_WndProc;
/* fill in window_class here */
RegisterClass(&window_class);

window = CreateWindow(
"My Application", "Stupidity",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, my_menu, instance, NULL);

// save the address of the class as the Window's USERDATA.
SetWindowLong(window, GWLP_USERDATA, (long)this);
}
};

With this, you derive a class from BaseWindow. In your derived class, you provide a "WindowProc" that overrides the (pure virtual) one in BaseWindow. The trick here is fairly simple: since you can't pass a parameter directly, you store the address of the class in the window's GWLP_USERDATA, then in the window proc (try to) retrieve that and use it to call the derived class' virtual window proc.

As an aside, note that this is a sketch, not a finished work (so to speak). Though it should compile as-is, the result won't actually work unless you fill in a fair number of pieces that aren't here (e.g., the other fields of the WNDCLASS structure being only one of the most obvious).



Related Topics



Leave a reply



Submit