El mejor método para almacenar este puntero para usar en WndProc

Me interesa saber cuál es la mejor forma de almacenar un puntero this para usar en el WndProc . Conozco varios enfoques, pero cada uno, según tengo entendido, tiene sus propios inconvenientes. Mis preguntas son:

¿Qué formas diferentes hay de producir este tipo de código?

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

Puedo pensar en Thunks, HashMaps, Thread Local Storage y la estructura de Window User Data.

¿Cuáles son los pros / contras de cada uno de estos enfoques?

Puntos otorgados por ejemplos de código y recomendaciones.

Esto es puramente por curiosidades. Después de usar MFC, me he estado preguntando cómo funciona y luego he pensado en ATL, etc.

Editar: ¿Cuál es el primer lugar en el que puedo usar válidamente el HWND en el proceso de ventana? Está documentado como WM_NCCREATE , pero si realmente experimentas, ese no es el primer mensaje que se envía a una ventana.

Editar: ATL usa un procesador para acceder al puntero. MFC utiliza una búsqueda HWND de HWND s.

En su constructor, llame a CreateWindowEx con “this” como el argumento lpParam.

Luego, en WM_NCCREATE, llame al siguiente código:

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

Luego, en la parte superior de su procedimiento de ventana puede hacer lo siguiente:

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

Lo que te permite hacer esto:

 wndptr->DoSomething(); 

Por supuesto, podría usar la misma técnica para llamar algo así como su función anterior:

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

… que luego puede usar su puntero “this” como se esperaba.

Si bien usar SetWindowLongPtr y GetWindowLongPtr para acceder al GWL_USERDATA puede sonar como una buena idea, recomiendo encarecidamente no utilizar este enfoque.

Este es exactamente el enfoque utilizado por el editor de Zeus y en los últimos años no ha causado más que dolor.

Creo que lo que sucede es que se envían mensajes de terceros a Zeus que también tienen establecido su valor GWL_USERDATA . Una aplicación en particular fue una herramienta de Microsoft que proporcionaba una forma alternativa de ingresar caracteres asiáticos en cualquier aplicación de Windows (es decir, algún tipo de utilidad de teclado de software).

El problema es que Zeus siempre asume que los datos de GWL_USERDATA fueron establecidos por él e intenta usar los datos como un puntero , lo que da como resultado un locking.

Si tuviera que hacerlo todo de nuevo con, lo que sé ahora, iría por un enfoque de búsqueda hash en caché, donde el asa de la ventana se utiliza como la clave.

Debería usar GetWindowLongPtr() / SetWindowLongPtr() (o GetWindowLong() / SetWindowLong() desuso). Son rápidos y hacen exactamente lo que quieres hacer. La única parte difícil es averiguar cuándo llamar a SetWindowLongPtr() : debe hacer esto cuando se envía el primer mensaje de ventana, que es WM_NCCREATE .
Consulte este artículo para obtener un código de muestra y una discusión más detallada.

El almacenamiento local de subprocesos es una mala idea, ya que puede tener varias ventanas ejecutándose en un subproceso.

Un mapa hash también funcionaría, pero calcular la función hash para cada mensaje de ventana (y hay MUCHO ) puede ser costoso.

No estoy seguro de cómo te refieres a usar thunks; ¿cómo estás pasando por los thunks?

He usado SetProp / GetProp para almacenar un puntero a los datos con la ventana misma. No estoy seguro de cómo se compara con los otros artículos que mencionaste.

Puede usar GetWindowLongPtr y SetWindowLongPtr ; use GWLP_USERDATA para adjuntar el puntero a la ventana. Sin embargo, si está escribiendo un control personalizado, le sugiero usar bytes de ventana adicionales para realizar el trabajo. Al registrar la clase de ventana, establezca WNDCLASS::cbWndExtra en el tamaño de los datos como este, wc.cbWndExtra = sizeof(Ctrl*); .

Puede obtener y establecer el valor usando GetWindowLongPtr y SetWindowLongPtr con el parámetro nIndex establecido en 0 . Este método puede guardar GWLP_USERDATA para otros fines.

La desventaja de GetProp y SetProp , habrá una comparación de cadenas para obtener / establecer una propiedad.

Con respecto a la seguridad SetWindowLong () / GetWindowLong (), según Microsoft:

La función SetWindowLong falla si la ventana especificada por el parámetro hWnd no pertenece al mismo proceso que la cadena de llamada.

Desafortunadamente, hasta el lanzamiento de una actualización de seguridad el 12 de octubre de 2004, Windows no aplicaría esta regla , permitiendo que una aplicación establezca el GWL_USERDATA de otra aplicación. Por lo tanto, las aplicaciones que se ejecutan en sistemas no actualizados son vulnerables al ataque a través de llamadas a SetWindowLong ().

En el pasado he usado el parámetro lpParam de CreateWindowEx :

lpParam [en, opcional] Tipo: LPVOID

Puntero a un valor que se pasará a la ventana a través de la estructura CREATESTRUCT (miembro lpCreateParams) señalado por el parámetro lParam del mensaje WM_CREATE. Este mensaje se envía a la ventana creada por esta función antes de que regrese. Si una aplicación llama a CreateWindow para crear una ventana de cliente MDI, lpParam debe apuntar a una estructura CLIENTCREATESTRUCT. Si una ventana de cliente MDI llama a CreateWindow para crear una ventana secundaria MDI, lpParam debe apuntar a una estructura MDICREATESTRUCT. lpParam puede ser NULL si no se necesitan datos adicionales.

El truco aquí es tener un std::map static de HWND para punteros de instancia de clase. Es posible que std::map::find sea ​​más SetWindowLongPtr que el método SetWindowLongPtr . Sin embargo es ciertamente más fácil escribir código de prueba usando este método.

Por cierto, si está usando un cuadro de diálogo win32, entonces necesitará usar la función DialogBoxParam .

Recomiendo establecer una variable thread_local justo antes de llamar a CreateWindow , y leerlo en su WindowProc para encontrar this variable (supongo que tiene control sobre WindowProc ).

De esta forma, tendrá la asociación this / HWND en el primer mensaje enviado a su ventana.

Con los otros enfoques sugeridos, es probable que se pierda algunos mensajes: los enviados antes de 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) { // ....