2013-08-10 16 views
6

Sto tentando di associare un membro di classe non statico a una funzione standard WNDPROC. So che posso farlo semplicemente rendendo statico il membro della classe. Ma, come studente di STL di C++ 11, sono molto interessato a farlo usando gli strumenti sotto l'intestazione <functional>.Come faccio `std :: bind` un membro di classe non statico a una funzione di callback Win32` WNDPROC`?

Il mio codice è come segue.

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

Quando eseguo così com'è, dà il messaggio di errore:

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

risposta

7

Mentre JohnB ha già spiegato i dettagli sul motivo per cui ciò non è possibile, ecco una soluzione comune al problema che si sta tentando di risolvere: concedere l'accesso di istanza di classe a un membro di classe statico.

Il principio guida alla soluzione è che un puntatore istanza deve essere conservata in un modo che sia accessibile al membro classe statica. Quando si ha a che fare con Windows, la memoria della finestra extra è un buon posto dove archiviare queste informazioni. Lo spazio richiesto per la memoria della finestra aggiuntiva viene specificato tramite WNDCLASSEXW::cbWndExtra mentre l'accesso ai dati viene fornito tramite SetWindowLongPtr e GetWindowLongPtr.

  1. Conservare un puntatore di istanza nella finestra di area di dati in più dopo la costruzione:

    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. recuperare il puntatore esempio dalla routine della finestra statica e chiamare nella funzione finestra membro procedura:

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

    La firma del membro della classe WindowProc è la stessa del codice che hai fornito.

Questo è un modo per implementare il comportamento desiderato.Remy Lebeau ha suggerito una variante a questo che ha il vantaggio di ottenere tutti i messaggi instradati attraverso il membro della classe WindowProc:

  1. allocare spazio nei dati aggiuntivi delle finestre (come sopra):

    void Create() 
    { 
        WNDCLASSEXW WindowClass; 
        // ... 
        // Assign the static WindowProc 
        WindowClass.lpfnWndProc = &MainWindow::StaticWindowProc; 
        // Reserve space to store the instance pointer 
        WindowClass.cbWndExtra = sizeof(MainWindow*); 
        // ... 
    
  2. passare il puntatore esempio per CreateWindowExW:

    m_hWnd = CreateWindowEx(/* ... */, 
              static_cast<LPVOID>(this)); 
        // SetWindowLongPtrW is called from the message handler 
    } 
    
  3. estratto puntatore di istanza e memorizzarlo nella finestra e zona xtra dati quando il primo messaggio (WM_NCCREATE) viene inviato alla finestra:

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

Nota 1: Il puntatore di istanza viene memorizzato nella finestra di area di dati in più dopo che la finestra è stata creata mentre il lpfnWndProc è impostato prima della creazione. Ciò significa che StaticWindowProc verrà chiamato mentre il puntatore dell'istanza non è ancora disponibile. Di conseguenza, è richiesto lo statuto if all'interno di StaticWindowProc in modo che i messaggi durante la creazione (come WM_CREATE) vengano gestiti correttamente.

Nota 1a: Le restrizioni indicate nella Nota 1 non si applicano all'attuazione alternativa. Il puntatore dell'istanza sarà disponibile in avanti dal primo messaggio e il membro della classe WindowProc verrà quindi chiamato per tutti i messaggi.

Nota 2: Se si vuole distruggere l'istanza della classe C++ quando il sottostante HWND è distrutto, WM_NCDESTROY è il posto giusto per farlo; è il messaggio finale inviato a qualsiasi finestra.

+2

Invece di chiamare SetWindowLongPtr() dopo CreateWindowEx(), è possibile passare il puntatore di istanza a CreateWindowEx() per sé e quindi chiamare SetWindowLongPtr() nel gestore di messaggi WM_NCCREATE. –

+0

@Remy Grazie per aver segnalato un'implementazione alternativa. Ho aggiornato la risposta per includere il tuo suggerimento. – IInspectable

+0

Se si utilizza 'GWLP_USERDATA' con' SetWindowLongPtr() ', non è necessario impostare' cbWndExtra' della finestra. –

1

che non si può fare questo, dal momento che WNDPROC è sinonimo di un puntatore a funzione. Ogni puntatore a funzione può essere convertito in una funzione std ::, ma non tutte le funzioni std :: rappresenta un puntatore a funzione.

prova dell'impossibilità del piano: Tecnicamente, WNDPROC rappresenta solo l'indirizzo della funzione in memoria che deve essere chiamato. Quindi una variabile di tipo WNDPROC non contiene "spazio" per memorizzare informazioni sui parametri associati.

sua lo stesso problema come il seguente esempio:

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 

} 

ancora per chiamare h da un CallingObject con un certo valore di parametro determinato in fase di esecuzione, è necessario memorizzare il valore di parametro in una variabile statica e poi scrivere una funzione wrapper chiamando lo h con questo valore come argomento.

Questo è il motivo per cui le funzioni di callback di solito prendono un argomento di tipo void *, in cui è possibile passare dati arbitrari necessari per il calcolo.