Il miglior metodo per memorizzare questo puntatore per l’utilizzo in WndProc

Sono interessato a conoscere il modo migliore / comune di memorizzare un puntatore per l’utilizzo in WndProc . Conosco diversi approcci, ma ognuno, a quanto ho capito, ha i propri svantaggi. Le mie domande sono:

Che modi diversi ci sono di produrre questo tipo di codice:

 CWindow::WndProc(UINT msg, WPARAM wParam, LPARAM) { this->DoSomething(); } 

Posso pensare a Thunks, HashMaps, Thread Local Storage e alla struttura dei dati utente di Windows.

Quali sono i pro / contro di ognuno di questi approcci?

Punti assegnati per esempi di codice e raccomandazioni.

Questo è puramente per amor di curiosità. Dopo aver usato MFC mi sono chiesto come funziona e poi ho pensato a ATL, ecc.

Modifica: Qual è il primo posto in cui posso validamente utilizzare l’ HWND nella finestra proc? È documentato come WM_NCCREATE , ma se in realtà si sperimenta, non è il primo messaggio ad essere inviato a una finestra.

Modifica: ATL utilizza un thunk per accedere a questo puntatore. MFC usa una ricerca hashtable di HWND s.

Nel costruttore, chiama CreateWindowEx con “this” come argomento lpParam.

Quindi, su WM_NCCREATE, chiama il seguente codice:

 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) ((CREATESTRUCT*)lParam)->lpCreateParams); SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); 

Quindi, nella parte superiore della finestra, puoi eseguire le seguenti operazioni:

 MyWindowClass *wndptr = (MyWindowClass*) GetWindowLongPtr(hwnd, GWL_USERDATA); 

Che ti permette di fare questo:

 wndptr->DoSomething(); 

Certo, potresti usare la stessa tecnica per chiamare qualcosa come la tua funzione sopra:

 wndptr->WndProc(msg, wparam, lparam); 

… che può quindi usare il suo puntatore “this” come previsto.

Mentre utilizzare SetWindowLongPtr e GetWindowLongPtr per accedere a GWL_USERDATA potrebbe sembrare una buona idea, consiglio vivamente di non utilizzare questo approccio.

Questo è esattamente l’approccio utilizzato dall’editor Zeus e negli ultimi anni non ha causato altro che dolore.

Penso che ciò che succede siano i messaggi Windows di terze parti inviati a Zeus che hanno anche il loro valore GWL_USERDATA impostato. Un’applicazione in particolare era uno strumento Microsoft che dimostrava un modo alternativo per inserire caratteri asiatici in qualsiasi applicazione Windows (ad esempio una sorta di utility per la tastiera software).

Il problema è che Zeus sempre presuppone che i dati GWL_USERDATA siano stati impostati da esso e tenta di utilizzare i dati come un puntatore , che quindi provoca un arresto anomalo.

Se dovessi fare tutto di nuovo con, quello che so ora, vorrei andare per un approccio di ricerca hash in cache in cui la maniglia della finestra viene utilizzata come chiave.

È necessario utilizzare GetWindowLongPtr() / SetWindowLongPtr() o il deprecato GetWindowLong() / SetWindowLong() ). Sono veloci e fanno esattamente quello che vuoi fare. L’unica parte difficile è capire quando chiamare SetWindowLongPtr() – È necessario farlo quando viene inviato il primo messaggio della finestra, che è WM_NCCREATE .
Vedi questo articolo per un codice di esempio e una discussione più approfondita.

L’archiviazione locale del thread è una ctriggers idea, poiché potresti avere più windows in esecuzione in un thread.

Anche una mappa hash funzionerebbe, ma il calcolo della funzione di hash per ogni messaggio della finestra (e ci sono molti ) può diventare costoso.

Non sono sicuro di come intendi usare i thunk; come stai passando i thunk?

Ho usato SetProp / GetProp per memorizzare un puntatore ai dati con la finestra stessa. Non sono sicuro di come si sovrappone agli altri elementi che hai citato.

È ansible utilizzare GetWindowLongPtr e SetWindowLongPtr ; utilizzare GWLP_USERDATA per colbind il puntatore alla finestra. Tuttavia, se si sta scrivendo un controllo personalizzato, suggerirei di utilizzare i byte di finestra aggiuntivi per completare il lavoro. Durante la registrazione della class della finestra, impostare WNDCLASS::cbWndExtra sulla dimensione dei dati come questo, wc.cbWndExtra = sizeof(Ctrl*); .

È ansible ottenere e impostare il valore utilizzando GetWindowLongPtr e SetWindowLongPtr con il parametro nIndex impostato su 0 . Questo metodo può salvare GWLP_USERDATA per altri scopi.

Lo svantaggio con GetProp e SetProp , ci sarà un confronto tra stringhe per ottenere / impostare una proprietà.

Per quanto riguarda la sicurezza SetWindowLong () / GetWindowLong (), secondo Microsoft:

La funzione SetWindowLong ha esito negativo se la finestra specificata dal parametro hWnd non appartiene allo stesso processo del thread chiamante.

Sfortunatamente, fino al rilascio di un aggiornamento di sicurezza il 12 ottobre 2004, Windows non avrebbe applicato questa regola , consentendo a un’applicazione di impostare GWL_USERDATA di altre applicazioni. Pertanto, le applicazioni eseguite su sistemi senza patch sono vulnerabili agli attacchi tramite chiamate a SetWindowLong ().

In passato ho utilizzato il parametro lpParam di CreateWindowEx :

lpParam [in, facoltativo] Tipo: LPVOID

Puntatore a un valore da passare alla finestra tramite la struttura CREATESTRUCT (membro lpCreateParams) a cui fa riferimento il parametro lParam del messaggio WM_CREATE. Questo messaggio viene inviato alla finestra creata da questa funzione prima che ritorni. Se un’applicazione chiama CreateWindow per creare una finestra client MDI, lpParam dovrebbe puntare a una struttura CLIENTCREATESTRUCT. Se una finestra del client MDI chiama CreateWindow per creare una finestra secondaria MDI, lpParam dovrebbe puntare a una struttura MDICREATESTRUCT. lpParam potrebbe essere NULL se non sono necessari dati aggiuntivi.

Il trucco qui è di avere una static std::map di HWND ai puntatori di istanza di class. È ansible che std::map::find sia più performante del metodo SetWindowLongPtr . È certamente più facile scrivere codice di prova usando questo metodo.

A proposito se si sta utilizzando una finestra di dialogo win32 allora sarà necessario utilizzare la funzione DialogBoxParam .

Ti consiglio di impostare una variabile thread_local prima di chiamare CreateWindow e leggerla nel tuo WindowProc per scoprire this variabile (presumo tu abbia il controllo su WindowProc ).

In questo modo avrai l’associazione / HWND sul primo messaggio inviato alla tua finestra.

Con gli altri approcci suggeriti qui è probabile che mancherai su alcuni messaggi: quelli inviati prima di WM_CREATE / WM_NCCREATE / WM_GETMINMAXINFO .

 class Window { // ... static thread_local Window* _windowBeingCreated; static thread_local std::unordered_map _hwndMap; // ... HWND _hwnd; // ... // all error checking omitted // ... void Create (HWND parentHWnd, UINT nID, HINSTANCE hinstance) { // ... _windowBeingCreated = this; ::CreateWindow (YourWndClassName, L"", WS_CHILD | WS_VISIBLE, x, y, w, h, parentHWnd, (HMENU) nID, hinstance, NULL); } static LRESULT CALLBACK Window::WindowProcStatic (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { Window* _this; if (_windowBeingCreated != nullptr) { _hwndMap[hwnd] = _windowBeingCreated; _windowBeingCreated->_hwnd = hwnd; _this = _windowBeingCreated; windowBeingCreated = NULL; } else { auto existing = _hwndMap.find (hwnd); _this = existing->second; } return _this->WindowProc (msg, wparam, lparam); } LRESULT Window::WindowProc (UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { // ....