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:
- Pass your instance pointer in the
lpParam
parameter ofCreateWindow
orCreateWindowEx
. - Read this value in your
WM_NCCREATE
handler. That message supplies the information as part of theCREATESTRUCT
struct. - Still in
WM_NCCREATE
callSetWindowLongPtr
to set the user data of the window to the instance pointer. - 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
.
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));
}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
:
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*);
// ...Pass instance pointer to
CreateWindowExW
:m_hWnd = CreateWindowEx( /* ... */,
static_cast<LPVOID>(this) );
// SetWindowLongPtrW is called from the message handler
}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
Correct Way to Define C++ Namespace Methods in .Cpp File
C/C++ Inline Assembler with Instructions in String Variables
Remove Duplicates from a List<Int>
Undefined Reference to Template Members
Vector Memory Allocation Strategy
Error: "No Match for Operator+" , for List Iterator
Enum Class Constructor C++ , How to Pass Specific Value
Special Characters in Visual Studio 2019 C++ Project and Executing Cmd Commands with Them
How to Make My Program in Qt Continually Send a String to My Arduino
Protected Data in Parent Class Not Available in Child Class
Why Is Copy Constructor Not Being Called in This Code
Move Element from Boost Multi_Index Array
Std::Cin Doesn't Throw an Exception on Bad Input
Size of the Classes in Case of Virtual Inheritance
How to Compare Char Variables (C-Strings)
"_Gfortran_Pow_C8_I4" Error When Linking .O Files from G++ and Gfortran Using G++