Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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".
like image 737
hkBattousai Avatar asked Aug 10 '13 12:08

hkBattousai


2 Answers

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.

like image 77
IInspectable Avatar answered Oct 17 '22 02:10

IInspectable


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.

like image 38
JohnB Avatar answered Oct 17 '22 04:10

JohnB