Как `std :: bind` нестатического члена класса с функцией обратного вызова Win32` WNDPROC`?

Я пытаюсь связать нестатический член класса со стандартом WNDPROC функция. Я знаю, что могу просто сделать это, сделав член класса статичным. Но, как ученик C ++ 11 STL, я очень заинтересован в том, чтобы сделать это с помощью инструментов под <functional> заголовок.

Мой код выглядит следующим образом.

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);
}
};

Когда я запускаю его как есть, выдается сообщение об ошибке:

Error: no suitable conversion function from "std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)>" to "WNDPROC".

6

Решение

Хотя JohnB уже объяснил детали, почему это невозможно, вот общее решение проблемы, которую вы пытаетесь решить: Предоставление экземпляру класса доступа к статическому члену класса.

Руководящий принцип решения заключается в том, что указатель экземпляра должен храниться так, чтобы он был доступен статическому члену класса. При работе с окнами дополнительная память окна является хорошим местом для хранения этой информации. Запрашиваемое пространство дополнительной памяти окна указывается через WNDCLASSEXW::cbWndExtra в то время как доступ к данным обеспечивается через SetWindowLongPtr а также GetWindowLongPtr.

  1. Сохраните указатель экземпляра в области дополнительных данных окна после построения:

    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. Получите указатель экземпляра из статической оконной процедуры и вызовите функцию-член оконной процедуры:

    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);
    }
    

    Подпись ученика WindowProc такой же, как в коде, который вы предоставили.

Это один из способов реализации желаемого поведения. Реми Лебо предложил вариант к этому, который имеет преимущество в том, что все сообщения маршрутизируются через ученика. WindowProc:

  1. Выделите место в окне дополнительных данных (так же, как указано выше):

    void Create()
    {
    WNDCLASSEXW WindowClass;
    // ...
    // Assign the static WindowProc
    WindowClass.lpfnWndProc = &MainWindow::StaticWindowProc;
    // Reserve space to store the instance pointer
    WindowClass.cbWndExtra  = sizeof(MainWindow*);
    // ...
    
  2. Передать указатель экземпляра на CreateWindowExW:

        m_hWnd = CreateWindowEx( /* ... */,
    static_cast<LPVOID>(this) );
    // SetWindowLongPtrW is called from the message handler
    }
    
  3. Извлечь указатель экземпляра и сохранить его в области дополнительных данных окна при появлении первого сообщения (WM_NCCREATE) отправляется в окно:

    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);
    }
    

Примечание 1: указатель экземпляра сохраняется в области дополнительных данных окна после того, как окно было создано, пока lpfnWndProc устанавливается до создания. Это означает, что StaticWindowProc будет вызываться, пока указатель экземпляра еще не доступен. Как следствие ifзаявление внутри StaticWindowProc требуется, чтобы сообщения при создании (например, WM_CREATE) получить должным образом обработаны.

Примечание 1а: ограничения, указанные в примечании 1, не применяются к альтернативному варианту реализации. Указатель экземпляра будет доступен, начиная с первого сообщения и члена класса WindowProc следовательно, будет вызываться для всех сообщений.

Примечание 2: если вы хотите уничтожить экземпляр класса C ++, когда основной HWND уничтожен, WM_NCDESTROY это место для этого; это последнее сообщение, отправленное в любое окно.

7

Другие решения

Думаю, вы не можете этого сделать, поскольку WNDPROC обозначает указатель на функцию. Каждый указатель на функцию может быть преобразован в std :: function, но не каждый std :: function представляет указатель на функцию.

Доказательство невозможности вашего плана: Технически WNDPROC представляет только адрес функции в памяти, которая должна быть вызвана. Следовательно, переменная типа WNDPROC не содержит «пробела» для хранения информации о связанных параметрах.

Это та же проблема, что и в следующем примере:

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

}

Еще для того чтобы позвонить h из CallingObject с некоторым значением параметра, определенным во время выполнения, вы должны сохранить значение параметра в статической переменной, а затем написать функцию-оболочку, вызывающую h с этим значением в качестве аргумента.

Вот почему функции обратного вызова обычно принимают аргумент типа void *где вы можете передать произвольные данные, необходимые для расчета.

1

По вопросам рекламы [email protected]