I'm trying to bind a non-static class member to a standard WNDPROC
function. I know I can simply do this by making the class member static. But, as a C++11 STL learner, I'm very interested in doing it by using the tools under the <functional>
header.
My code is as follows.
class MainWindow
{
public:
void Create()
{
WNDCLASSEXW WindowClass;
WindowClass.cbSize = sizeof(WNDCLASSEX);
WindowClass.style = m_ClassStyles;
WindowClass.lpfnWndProc = std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)>
( std::bind(&MainWindow::WindowProc,
*this,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4));
WindowClass.cbClsExtra = 0;
WindowClass.cbWndExtra = 0;
WindowClass.hInstance = m_hInstance;
WindowClass.hIcon = LoadIconW(m_hInstance, MAKEINTRESOURCEW(IDI_WINDOW));
WindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WindowClass.hbrBackground = (HBRUSH) COLOR_WINDOW;
WindowClass.lpszMenuName = MAKEINTRESOURCEW(IDR_MENU);
WindowClass.lpszClassName = m_ClassName.c_str();
WindowClass.hIconSm = LoadIconW(m_hInstance, MAKEINTRESOURCEW(IDI_WINDOW_SMALL));
RegisterClassExW(&WindowClass);
m_hWnd = CreateWindowEx(/*_In_ DWORD*/ ExtendedStyles,
/*_In_opt_ LPCTSTR*/ m_ClassName.c_str(),
/*_In_opt_ LPCTSTR*/ m_WindowTitle.c_str(),
/*_In_ DWORD*/ m_Styles,
/*_In_ int*/ m_x,
/*_In_ int*/ m_y,
/*_In_ int*/ m_Width,
/*_In_ int*/ m_Height,
/*_In_opt_ HWND*/ HWND_DESKTOP,
/*_In_opt_ HMENU*/ NULL,
/*_In_opt_ HINSTANCE*/ WindowClass.hInstance,
/*_In_opt_ LPVOID*/ NULL);
}
private:
LRESULT CALLBACK WindowProc(_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
};
When I run it as is, it gives the error message:
Error: no suitable conversion function from "std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)>" to "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.
Guess you cannot do that, since WNDPROC stands for a function pointer. Every function pointer can be converted to a std::function, yet not every std::function represents a function pointer.
Proof of impossibility of your plan: Technically, WNDPROC represents only the address of the function in memory which is to be called. Hence a variable of type WNDPROC does not contain "space" to store information about bound parameters.
Its the same problem as in the following example:
typedef void (* callbackFn) ();
struct CallingObject {
callbackFn _callback;
CallingObject (callbackFn theCallback) : _callback (theCallback) {
}
void Do () {
_callback ();
}
};
void f () { std::cout << "f called"; }
void g () { std::cout << "g called"; }
void h (int i) { std::cout << "h called with parameter " << i; }
int main () {
CallingObject objF (f); objF.Do (); // ok
CallingObject objG (g); objG.Do (); // ok
}
Yet in order to call h
from a CallingObject
with some parameter value determined at runtime, you must store the parameter value in a static variable and then write a wrapper function calling h
with this value as argument.
That's the reason callback functions usually take an argument of type void *
, where you can pass arbitrary data needed for the calculation.