WndProc als Member - SetWindowLongPtr vs std::unordered_map



  • Hallo,

    ich würde mir gerne eine Fenster-Klasse schreiben, welche eine WndProc als Memberfunktion hat (also auf den this-Pointer zugreifen kann). Das geht ja nicht direkt, da die erwartete Signatur der WndProc keinen this-Pointer entgegennimmt.

    Nach Recherche bei Google habe ich (im Grunde) zwei Möglichkeiten gefunden: Einmal mittels Set/GetWindowLongPtr und GWL_USERDATA den this-Pointer speichern, oder selbigen in einer map (z.B. std::unordered_map) mit dem Fenster als Key speichern.

    Meine Frage ist jetzt welcher Methode ist der Vorzug zu geben?

    Bei der Get/SetWindowLongPtr Methode habe ich Bedenken dass andere Anwendungen selbst die GWL_USERDATA benutzen/modifizieren was dann natürlich mein Programm kaputt machen würde.

    Bei der Variante mit der map habe ich Bedenken dass das Performance-technisch ein Problem werden kann wenn sehr viele Window-Messages verarbeitet werden müssen. Denn bei meiner Anwendung werden sehr viele Messages erzeugt. Auch ist eine map ja typischerweise für etliche Elemente gedacht und ich werde jetzt nur sehr wenige Fenster-Instanzen erzeugen (Millionen von offener Fenster machen ja auch keinen Sinn), daher hab ich Zweifel ob das der richtige Weg wäre.

    Also, was wäre der beste Weg? Irgendwelche Tipps, Anmerkungen oder Empfehlungen wären nett 🙂



  • Von der Performance her hätte ich weniger Bedenken, aber globale Datenstrukturen finde ich doof. Ist aber vermutlich die einfachste Lösung. Wenn du die Zugriffe auf diese Map entsprechend absicherst (Mutex), sollte es auch kein Problem geben wenn es mal mehrere GUI Threads gibt.

    Dritte Möglichkeit: Thunks erzeugen (ATL macht das z.B. so).
    D.h. du forderst Speicher an und generierst da drinnen die nötigen Maschinenbefehle um den this Zeiger zu setzen und dann zur gewünschten Memberfunktion weiterzuspringen.
    Dann markierst du den Speicherbereich als "ausführbar", und verwendest ihn als WindowProc.



  • hustbaer schrieb:

    Von der Performance her hätte ich weniger Bedenken, aber globale Datenstrukturen finde ich doof. Ist aber vermutlich die einfachste Lösung. Wenn du die Zugriffe auf diese Map entsprechend absicherst (Mutex), sollte es auch kein Problem geben wenn es mal mehrere GUI Threads gibt.

    Hm, was meinst du mit globaler Datenstruktur? Also ich würde die map schon private und in der Klasse anlegen, so dass von außen keiner darauf zugreifen kann (nicht als globale Variable).

    hustbaer schrieb:

    Dritte Möglichkeit: Thunks erzeugen (ATL macht das z.B. so).
    D.h. du forderst Speicher an und generierst da drinnen die nötigen Maschinenbefehle um den this Zeiger zu setzen und dann zur gewünschten Memberfunktion weiterzuspringen.
    Dann markierst du den Speicherbereich als "ausführbar", und verwendest ihn als WindowProc.

    Ok, davon hab ich noch nie gehört, werde ich mal googlen.



  • happystudent schrieb:

    hustbaer schrieb:

    Von der Performance her hätte ich weniger Bedenken, aber globale Datenstrukturen finde ich doof. Ist aber vermutlich die einfachste Lösung. Wenn du die Zugriffe auf diese Map entsprechend absicherst (Mutex), sollte es auch kein Problem geben wenn es mal mehrere GUI Threads gibt.

    Hm, was meinst du mit globaler Datenstruktur? Also ich würde die map schon private und in der Klasse anlegen, so dass von außen keiner darauf zugreifen kann (nicht als globale Variable).

    Darum geht's mir nicht. Ich mag auch keine "Klassenvariablen".
    Member = gut, static/global/... = böse.

    Aber manchmal geht's halt nicht anders - bzw. nicht mit vertretbarem Aufwand.



  • hustbaer schrieb:

    Darum geht's mir nicht. Ich mag auch keine "Klassenvariablen".
    Member = gut, static/global/... = böse.

    Aber manchmal geht's halt nicht anders - bzw. nicht mit vertretbarem Aufwand.

    Ach so, weil die map dann static wäre quasi... Ja, das stimmt. Hab schonmal kurz in die Methode mit thunks reineschaut und denke das ist mir zu kompliziert 😃

    Werd dann wohl die map nehmen, scheint mir am sichersten zu sein.



  • happystudent schrieb:

    Bei der Get/SetWindowLongPtr Methode habe ich Bedenken dass andere Anwendungen selbst die GWL_USERDATA benutzen/modifizieren was dann natürlich mein Programm kaputt machen würde.

    Wie kann das denn passieren (also deine Windows-Struktur zerstören)?



  • coder777 schrieb:

    Wie kann das denn passieren (also deine Windows-Struktur zerstören)?

    Naja, ich würde ja den Pointer speichern. Das heißt, wenn zu einem späteren Zeitpunkt meine WndProc aufgerufen wird, lese ich den Pointer wieder aus und caste ihn zu dem this-Pointer meiner Klasse.

    Wenn jetzt eine andere Anwendung einfach auch SetWindowLongPtr benutzt um irgendwelche infos zu speichern, würde mein Pointer ja überschrieben. Und beim Versuch dann zu casten würde es dementsprechend abstürzen.



  • Für SetWindowLong() sagt Microsoft:

    MSDN schrieb:

    The SetWindowLong function fails if the window specified by the hWnd parameter does not belong to the same process as the calling thread.

    und für SetWindowLongPtr():

    MSDN schrieb:

    When compiling for 32-bit Windows, SetWindowLongPtr is defined as a call to the SetWindowLong function.

    also gehe ich stark davon aus, dass eine andere Anwendung die privaten Daten meines Fenster nicht ändern kann.



  • MMn. geht es nicht um andere Anwendungen, sondern um anderen Code in der eigenen Anwendung.
    Sobald eine Framework-Klasse/Basisklasse etwas wie Get/SetWindowLongPtr verwendet, müssen alle User dieser Klasse das wissen/verstehen und sich danach richten.
    Weil es sonst ganz schnell Konflikte mit den IDs/Offsets gibt.
    Erschwert auch ganz speziell das Portieren von Code auf die eigene GUI Library.

    BTW: Die ID Konflikte liessen sich mit SetProp/GetProp/RemoveProp umgehen.



  • coder777 schrieb:

    Für SetWindowLong() sagt Microsoft:

    MSDN schrieb:

    The SetWindowLong function fails if the window specified by the hWnd parameter does not belong to the same process as the calling thread.

    Das gilt nur für Windows XP/2000. Man kann auf jeden Fall SetWindowLongPtr benutzen um Anwendungen in anderen Prozessen zu manipulieren, hab ich selbst schon gemacht 😃

    hustbaer schrieb:

    MMn. geht es nicht um andere Anwendungen, sondern um anderen Code in der eigenen Anwendung.
    Sobald eine Framework-Klasse/Basisklasse etwas wie Get/SetWindowLongPtr verwendet, müssen alle User dieser Klasse das wissen/verstehen und sich danach richten.

    Theoretisch schon, leider halten sich nicht alle Anwendungen daran (bzw haben dann deren Entwickler das vermutlich nicht bedacht) 😞

    hustbaer schrieb:

    BTW: Die ID Konflikte liessen sich mit SetProp/GetProp/RemoveProp umgehen.

    Hm, aber da hat man dann halt string-Vergleiche drinnen, was ich bei zig tausenden Messages/Sekunde gerne vermeiden würde...



  • happystudent schrieb:

    hustbaer schrieb:

    BTW: Die ID Konflikte liessen sich mit SetProp/GetProp/RemoveProp umgehen.

    Hm, aber da hat man dann halt string-Vergleiche drinnen, was ich bei zig tausenden Messages/Sekunde gerne vermeiden würde...

    Wir reden hier von GUI, das ist eh lahm, ein paar String-Vergleiche machen da wirklich nichts.



  • Nathan schrieb:

    Wir reden hier von GUI, das ist eh lahm, ein paar String-Vergleiche machen da wirklich nichts.

    Dann kann man aber auch gleich ne map nehmen, ist dann wahrscheinlich auch schneller...

    Mir gefällt dieses Get/SetProp schon allein deswegen nicht, weil es ja auch nur die Wahrscheinlichkeit für Konflikte verringert (bitte korrigieren, falls das falsch sein sollte) - schließlich könnte ja eine externe Anwendung den gleichen String für SetProp verwenden und würde dann ja wieder meine Daten überschreiben.



  • Ich würde den GWL_USERDATA Weg empfehlen; eine globale Map ist imo allein schon darum ein absolutes No-go, weil es eine globale Map wäre. Wegen GWL_USERDATA hätte ich keine Bedenken, da dessen Verwendung der Anwendung die das Fester erzeugt hat vorbehalten ist...

    Was rein nur Performance betrifft, wäre die beste Lösung wohl ein Thunk, weil Set/GetWindowLongPtr() einen System Call absetzen müssen, das ist allerdings wesentlich komplizierter und compiler- und architekturspezifisch und die praktische Relevanz einer kleinen Effizienzsteigerung an dieser Stelle ist imo zweifelhaft...



  • dot schrieb:

    Ich würde den GWL_USERDATA Weg empfehlen; eine globale Map ist imo allein schon darum ein absolutes No-go, weil es eine globale Map wäre.

    Was ist daran so schlimm? Wenn sonst keiner darauf Zugriff hat (weil private) und alle Zugriffe thread-safe implementiert sind sollte das doch egal sein?

    Ich meine, ein Compiler/ein Programm muss doch auch sowas ähnliches haben um die this-Pointer von allen zur Zeit existierenden Klassen-Instanzen zu verwalten, das ist ja dann das selbe (oder täusche ich mich da)?

    dot schrieb:

    Wegen GWL_USERDATA hätte ich keine Bedenken, da dessen Verwendung der Anwendung die das Fester erzeugt hat vorbehalten ist...

    Das Problem ist dass ich das Fenster selbst in einer fremden Anwendung (mittels DLL-Injection) erzeuge (welche über Plug-Ins erweiterbar ist) und ich daher nicht ausschließen kann dass GWL_USERDATA doch modifiziert wird.



  • Meiner Meinung nach ist was so ziemlich alles betrifft ein Thunk die beste Lösung (ausser halt Implementierungsaufwand).

    GWL_USERDATA würde ich auf Grund der ID Konflikte nicht verwenden wollen.

    Dann noch eher GetProp mit nem registrierten ATOM als Name, mit String = GUID (=> realistischerweise keine Chance Konflikte zu bekommen, es sei denn jemand macht es absichtlich).

    Oder eben die globale Map.


Log in to reply