2013-08-10 5 views
6

Я пытаюсь связать нестатический член класса с стандартной функцией WNDPROC. Я знаю, что могу просто сделать это, поставив член класса статическим. Но, как ученик C++ 11 STL, я очень заинтересован в этом, используя инструменты под заголовком <functional>.Как «std :: bind» нестатический член класса для функции обратного вызова Win32 `WNDPROC`?

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

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". 

ответ

7

В то время как 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. Extract указатель экземпляра и сохранить его в окне е область Xtra данных, когда первое сообщение (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) корректно обрабатывались.

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

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

+2

Вместо вызова SetWindowLongPtr() после CreateWindowEx() вы можете передать указатель экземпляра самому CreateWindowEx(), а затем вызвать SetWindowLongPtr() в обработчике сообщений WM_NCCREATE. –

+0

@Remy Спасибо, что указали альтернативную реализацию. Я обновил ответ, чтобы включить ваше предложение. – IInspectable

+0

Если вы используете 'GWLP_USERDATA' с' SetWindowLongPtr() ', вам вообще не нужно устанавливать' cbWndExtra' окна. –

1

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

Доказательство невозможности вашего плана: Технически 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 *, где вы можете передавать произвольные данные, необходимые для расчета.

 Смежные вопросы

  • Нет связанных вопросов^_^