How is "std :: bind" a non-static class member for the Win32 callback function "WNDPROC"?

I am trying to associate a non-static member of a class with the standard WNDPROC function. I know that I can just do this by setting the class member to static. But, as a student of C ++ 11 STL, I am very interested in this, using the tools under the <functional> heading.

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 an error message:

 Error: no suitable conversion function from "std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)>" to "WNDPROC". 
+6
source share
2 answers

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

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

  • Save the instance pointer in the additional data area of ​​the window 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 the window 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 a member of the WindowProc class is the same as in the code you specified.

This is one way to implement the desired behavior. Remy Lebo proposed an option for this, which has the advantage of receiving all messages routed through a member of the WindowProc class:

  • Allocate space in the additional data window (as described 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 } 
  • Retrieve the instance pointer and save it in the additional data area of ​​the window when the first message ( WM_NCCREATE ) is sent to the address of 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 in the additional data area of ​​the window after the window has been created and lpfnWndProc set before creation. This means that StaticWindowProc will be called while the instance pointer is not yet available. Therefore, an if -stance is required inside StaticWindowProc so that messages are correctly processed during creation (for example, WM_CREATE ).

Note 1a: The restrictions specified in Note 1 do not apply to an alternative implementation. The instance pointer will be available to jump from the first message, and so a member of the WindowProc class will be called for all messages.

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

+7
source

Guess you can't do this, since WNDPROC means a function pointer. Each function pointer can be converted to std :: function, but not every std :: function represents a function pointer.

Proof of the impossibility of your plan: Technically, WNDPROC is just the address of a function in memory that needs to be called. Therefore, a variable of type WNDPROC does not contain a “space” for storing information about related 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 } 

However, to call h from CallingObject with some parameter value defined at runtime, you must store the parameter value in a static variable and then write a wrapper function that calls h with that value as an argument.

For the reason callback functions usually take an argument of type void * , where you can pass arbitrary data needed for calculation.

+1
source

Source: https://habr.com/ru/post/951458/


All Articles