C++ProgrammierenWindows APIMartin Richter - Do 23 Okt 2008 19:35

Im Endeffekt ist es der gleiche Grund warum man GetKeyState und nicht GetAsynchKeyState verwenden sollte (siehe Die Unsitte GetAsyncKeyState statt GeyKeyState zu verwenden…).

Auch hier liefert GetCursorPos die aktuelle Mauszweiger Position während GetMessagePos die Position liefert in dem Moment in dem die aktuelle Windows Nachricht eingeliefert wurde.
Oft genug wird hier kein Unterschied sein, aber durch aufwendigere Berechnungen oder gar durch eine verzögerte Abarbeitung von Windows Nachrichten (lange Zeit wurde evtl. keine Nachrichtenschleife mehr abgearbeitet), kann es sehr wohl passieren, dass ein Mausklick für eine Position eingeliefert wird, der (Millisekunden) her ist, während die aktuelle Mausposition schon Millimeter weiter ist.

Ist ein User dann richtig schnell und hektisch, kann die folgende Kombination fatale Folgen bei Verwendung von GetCursorPos haben.

  • System ist etwas beschäftigt und die Nachrichtenschleife wird nicht zeitnah abgearbeitet
  • User klickt mit Maus auf Item1
  • Die Maus wird etwas weiter bewegt und steht nun auf Item2
  • Verzögertes Abarbeiten beginnt jetzt und das Programm ermittelt mit GetCursorPos Item2 und selektiert es.
  • User drückt Entf-Taste und wundert sich 😮 dass Item2 weg ist!

Wäre GetMessagePos hier verwendet worden, wäre das korrekte Item1 gelöscht worden.

Das Verwechseln dieser beiden Funktionen hatte mir in einem Stück Software sogar einen richtigen Crash beschert. Wir hatten in einem bestimmten Fenster eigentümliche Abstürze. Er trat immer auf wenn bestimmte Leute (Kategorie Poweruser oder Übernervös) mit der Maus bestimmte Teile in der Auswertung markierten bzw. selektierten.

Ein Programmierer nutze GetMessagePos, der andere GetCursorPos für einige Kalkulationen. Meistens ist kein großer Unterschied zwischen den beiden Werten aber manchmal kam es schon vor. Effekt war aber hier, dass die eine Routine mit einem Objekt eine Funktion einleitete und an anderer Stelle ein anderes Objekt ermittelt wurde. Und gerade einige hektische Leute, die Klicken während Sie noch nicht mal genau gezielt haben, brachten es zustande, dass es einen Unterschied gab zwischen der ursprünglichen Position (bei der ersten auslösenden Nachricht) und der aktuellen Mausposition (wenn die Nachricht dann abgearbeitet wurde).

C++CommunityMFCProgrammierenMartin Richter - So 07 Sep 2008 18:37

Es scheint mir meistens ein Anfängerfehler zu sein, dass viele MFC Entwickler (oder solche die es werden wollen) zuerst mal zu einer dialogbasierenden Anwendung greifen.
Ist ja auch nett. Man kümmert sich nur um die paar Dialogfelder und hat kein Doc/View zu verwalten.

Am Ende kommen aber dann noch weitere Wünsche:

  • Ich hätte gerne ein Menü
  • Ich hätte gernenoch einen Toolbar
  • Ich hätte gerne einen Status Bar
  • Schön wäre ein Accelerator

Triviales und nicht triviales schließt sich an:

  • Warum kann ich kein Command/Routing für meinen Toolbar und mein Menü verwenden?
  • Warum schließt mein Dialog bei Nutzung Eingabe-Taste?
  • Kann ich meinen Dialog auch resizen?
  • Die Daten sollen auch gespeichert werden, wie geht das?
  • Ich hätte gerne so einen schönen MFCNext Toolbar in meinem Dialog, oder ein Ribbon, geht das?
  • … (die Liste ist bestimmt nicht vollständig)

Gibt man dann die Antwort, dass man x Klimmzüge machen muss um so etwas in eine dialogbasierende Anwendung einzubauen (wenn es überhaupt geht), dann erntet man noch noch die stöhnende Klage: „Ohhh Mann! Ist das kompliziert!“

Und all das geht nur mit Mühe in einen CDialog einzubauen. Der Grund ist einfach: Das Commandrouting für all diese Elemente ist in CFrameWnd integriert. Aber ein CDialog leitet sich von CWnd ab und ist von Grunde auf für keine dieser Funktionen vorbereitet.

Dabei könnte alles so einfach sein!
Man muss nur einfach eine SDI Applikation mit einem CFormView erzeugen und alle die Wünsche die mancher später hat, kann man sofort erfüllen und wenn man es wirklich nicht will auch weglassen.

Die Frage stellt sich für mich also:
Warum nicht einfach immer gleich eine SDI/CFormView Anwendung bauen ❓
Das Potential dieser Anwendungsform ist einfach unerreichbar verglichen mit einer dialogbasierenden Anwendung.

Also sollte man sich mal die Liste der Wünsche, die ich hier aufgestellt habe ansehen und als Checkliste betrachten. Sollte einer dieser oben aufgeführten Punkte für die Applikation wichtig sein, würde ich dringend anraten zum SDI/CFormView zu greifen.
Das ist auch der Fall, wenn diese Anforderungen erst später integriert werden soll. Oft genug ist ja die dialogbasierende Anwendung schon fertig und es heißt dann: Ich möchte doch nur noch…

PS: Ich persönlich benutze nicht mal mehr zu Testzwecken dialogbasierende Anwendungen 😉

C++ProgrammierenWindows APIMartin Richter - Mi 11 Jun 2008 20:45

Immer wieder wird in Foren gefragt, wie man  feststellen kann, ob z.B. die Umschalt- oder Strg-Taste gedrückt ist.
Nur zu oft liest man als Antwort: Nimm GetAsyncKeyState ❗

Zwei Dinge stören mich an dieser Antwort:

  1. Dem Frager ist meistens gar nicht bewusst, dass es für die entsprechenden Tasteneingaben immer auch eine entsprechende Nachricht WM_KEY… Nachricht gibt.
    Hier wäre ein Grundstudium der Windows API angesagt!
  2. Ist den meisten Fragern (und Antwortenden) nicht klar, das Nachrichten chronologisch einlaufen aber GetAsyncKeyState den aktuellen Zustand der Taste zurückgibt, eben asynchron, wie der Name es schon andeutet.

In den meisten Fällen ist diese Antwort also falsch!

Im Detail will ich das auch erklären:
GetAsyncKeyState liefert den Zustand der Umschalt-Taste zu dem Zeitpunkt an dem die Funktion aufgerufen wird. Im Gegensatz dazu liefert GetKeyState den Zustand der Taste zu der Zeit, als die Nachricht die aktuell bearbeitet wird einlief, bzw. erzeugt wurde. (Genau genommen geht es hier um die letzte Windows-Nachricht)
Das Problem wird offensichtlich, wenn das OS durch hohe Prozessorauslastung Nachrichten etwas verzögert abarbeitet. Es ist klar, dass GetAsyncKeyState zu falschen Ergebnissen führen muss, bzw. ein evtl. gedrückte Strg-Taste evtl. nicht beachtet wird. Die Folge könnte sein, dass aus einer Kopieraktion, ein Verschieben wird. Fatal für den Benutzer.

Eigentlich fällt mir gar kein vernünftiger Grund ein GetAsyncKeyState zu verwenden! Außer man pollt, und pollen ist das Letzte was ich in eine Windows Applikation machen wollte.

Vermutlich hat irgendwann irgendein etwas unterbemittelter Programmierer angefangen einen Tipp mit GetAsyncKeyState zu geben und dieser Unsinn kursiert nun auf ewig durch die Netze…

ATLC++CRTMFCProgrammierenWindows APIMartin Richter - Di 08 Jan 2008 22:05

Immer wieder sieht man Code wie diesen:

CString strText = _T("xyz"); 
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"), 
                           strText, 4711);

Sieht harmlos aus und funktioniert. Warum eigentlich?

Es funktioniert nur aus einem einzigen Grund:
CString ist 4 Bytes groß, besteht nur aus einem einzigen Element und das ist ein Zeiger auf einen TCHAR Array!

Das ganze funktioniert sofort nicht mehr wenn wir eine eigene CMyString Klasse ableiten, der wir eine virtuelle Funktion zuordnen. Was passiert nun?
Nun ist CString nicht mehr 4 Bytes groß sondern 8 Bytes und besteht aus zwei Zeigern. Einem Zeiger auf eine vtable und einen Zeiger auf einen TCHAR Array.

Nicht nur wird jetzt kein Text mehr ausgegeben, sondern auch die Zahl wird nicht korrekt ausgegeben! Das ganze obwohl CMyString immer noch einen Umwandlungsoperator für LPCTSTR hat.

Das Problem ist aber, dass _tprintf eine Variable Anzahl von Argumenten (Ellipsis) hat und der C/C++ Compiler gar nicht weiß, was _tprintf erwartet. Also wird das ganze Objekt auf den Stack geschoben.

Man macht eine kleine Änderung, und schon geht die Sache in die Hose. Das habe ich ja schon in dem Artikel Die Cx2y Falle beschrieben. Oder sollte also so etwas wie eine Änderung der Implementierung von CString erfolgen würde solch ein Programm auch sofort nicht mehr funktionieren. Man muss jedoch vermuten, dass Microsoft sich das gar nicht erlauben würde, weil zu viele Entwickler solchen nicht portablen und unabhängigen Code verwenden.

Wie macht man es richtig? Man verwendet den entsprechenden cast oder eine Funktion, die den entsprechenden Typ returniert.

CString strText = _T("xyz"); 
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"), 
                       static_cast<LPCTSTR>(strText), 4711); 
// -- oder -- 
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"), 
                       strText.GetString(), 4711);

Man sollte grundsätzlich bei der Verwendung von einer variablen Anzahl von Argumenten (Ellipsis) immer auf den Typ casten, der auch letzten Endes erwartet wird.

C++ProgrammierenWindows APIMartin Richter - Sa 05 Jan 2008 22:36

Immer wieder tauchen in den Foren die Frage auf wie man vermeiden kann, das Seiten durch den Windows Memory Manager ausgelagert werden können.

Ursache für diese Frage ist der vermeintliche Glaube von einigen Entwicklern, dass man durch Vermeiden/Verbieten der Auslagerung von Speicherseiten, die Performance eines Programmes erhöhen könnte.

Das ist natürlich Unfug! 

Grundsätzlich sollte man die Dokumentation von VirtualLock beachten:

Locking pages into memory may degrade the performance of the system by reducing the available RAM and forcing the system to swap out other critical pages to the paging file. Each version of Windows has a limit on the maximum number of pages a process can lock. This limit is intentionally small to avoid severe performance degradation. Applications that need to lock larger numbers of pages must first call the SetProcessWorkingSetSize function to increase their minimum and maximum working set sizes. The maximum number of pages that a process can lock is equal to the number of pages in its minimum working set minus a small overhead.

Der Memory Manager weiß weitaus besser, was auszulagern ist und was nicht. Letzten Endes werden nur Seiten ausgelagert, die in der letzten Zeit nicht benötigt wurden. Ist ein Programm aktiv und nutzt den Speicher auch, besteht keine Gefahr, dass dessen Seiten ausgelagert werden.

Umgekehrt schränkt VirtualLock den Spielraum des Memory Managers ein. Werden Sie sinnlos im Speicher festgenagelt, dann müssen aktive Seiten ausgelagert werden und dass senkt meistens die Performance des gesamten Systems, weil nun unnötigerweise ausgelagert werden muss. Was nützt es, wenn das eigene Programm noch einigermaßen performant ist, aber ein Taskwechsel in den Explorer dann Minuten dauert?
Wie schon gesagt: Oft ist der Effekt genau entgegengesetzt!

Meine Erfahrung, selbst bei sehr speicherhungrigen Programmen ist:
Je weniger man Windows in die Quere kommt um so besser verhält sich das gesamte System.

Und bevor man sich an Funktionen wie VirtualLock versucht, sollteman evtl. eher den Speicherhunger seiner Programme eindämmen, oder den Performancemonitor bemühen, um heraus zu bekommen wo wirklich der Bottleneck ist. Die Perfomancewerte, die am meisten Auskunft über das Auslagern von Seiten geben sind im Abschnitt Memory (Speicher): Pages Input/sec (Seitenlesevorgänge/s) und Pages Output/Sec (Seiten-Schreibvorgänge/s). (Man beachte die inkonsistente Übersetzung 😉 !)
Pages Output/Sec (Seiten-Schreibvorgänge/s) ist mit Abstand der beste Indikator um Speicherkanppheit und Auslagerungsproblemen aufzuspüren!

Als Anmerkung sei noch hinzugefügt, dass VirtualLock Windows nicht hindert die Seiten dennoch auszulagern. Mehr Infos dazu hier, wobei dieser Artikel genauso auf die falsche Anwendung von VirtualLock hinweist:
http://blogs.msdn.com/oldnewthing/archive/2007/11/06/5924058.aspx

Ein guter Artikel über das Windows Speichermanagement findet sich noch hier:
http://members.shaw.ca/bsanders/WindowsGeneralWeb/RAMVirtualMemoryPageFileEtc.htm

C++MFCProgrammierenWindows APIMartin Richter - Do 27 Dez 2007 10:25

Dies ist eine Ergänzung zu meinem Blog Eintrag Die Unsitte Windows interne Nachrichten zu versenden.
In einem der Kommentare wurde ich gefragt wie man es nun mit WM_KEY… Nachrichten richtig macht, weil ich auch diese Nachrichten als „intern“ definiert habe. In meinen Augen ist es unzulässig WM_KEY… Nachrichten per PostMessage oder SendMessage zu versenden. Also ist die Frage berechtigt:
Wie macht man es richtig, mit der Simulation von Tastatureingaben für andere Prozesse ❓

Ersteinmal möchte ich erklären, warum das Senden einer WM_KEY… Nachricht nicht funktioniert, oder oft genug nicht richtig funktioniert:

  1. Alleine wenn das Programm GetKeyState oder GetAsynchKeystate benutzt um zu ermitteln ob gleichzeitig sie Strg- oder die Umschalttaste gedrückt wird, dann funktioniert SendMessage/PostMessage nicht mehr.
  2. Wenn das fremde Programm Accelerator verwendet, dann wird hier auch durch senden einer WM_KEY… Nachricht der Accelerator oft genug nicht ausgelöst. Die Folge: Der gewünschte Effekt bleibt aus.
  3. Man bekommt schnell Probleme mit synthetisierten Zeichen, die zwei Tasteneingaben erfordern, z.B. ^ und a, für â.
  4. Man muss fürchterlich darauf achten, wenn ein Befehl den Fokus wechselt.
  5. Die Applikation hat nicht den Fokus! Alleine das kann schon Fehlverhalten auslösen, denn normalerweise kann nur eine aktive Applikation mit Fokus Tastatur- und Mauseingaben erhalten.
  6. WM_SYSKEY… Nachrichten werden nicht korrekt erzeugt.

Wie macht man es nun ❓  
Es ist ganz einfach: Man benutzt SendInput ❗  
Der Vollständigkeit halber sei hier noch die legacy Funktion keybd_event erwähnt.

SendInput reiht die Eingaben ein in die Eingabequeue für das gesamte Windows System ein und liefert diese an die aktive Applikation aus und an das Fenster, das den aktuellen Eingabefokus hat. Man kann also nicht einfach so auch das Zielfenster angeben, dass die Eingaben erhalten soll. Das merkt man schon daran, dass es kein Fensterhandle gibt, dass man als Ziel angeben könnte für SendInput.

Frage aber nun: Wie gewährleistet man, dass das richtige Programm die Tastaturnachricht bekommt  ❓

Auch hier ist die Antwort relativ einfach! Zwei Dinge sind dazu nötig:

  1. Dass Zielfenster, bzw. den Thread in dem das Zielfenster muss ausgewählt werden, damit er diese Eingaben auch erhalten darf. Das wird durch AttachThreadInput erreicht. Denn wir wollen die Daten ja nicht an unsere Applikation senden. ❗ Bitte hinterher nicht vergessen AttatchThreadInput mit FALSE aufzurufen und den Thread wieder zu detachen.
  2. Man muss den Fokus korrekt setzten für das Fenster, dass die Eingaben erhalten soll!
    Ist der Zielthread jetzt an die Eingabequeue angeschlossen, kann man ohne Probleme SetFocus ausführen (was ohne AttachThreadInput normalerweise auch nicht möglich wäre).
  3. Wird jetzt SendInput ausgeführt, werden alle Nachrichten korrekt erzeugt und auch alle Commands entsprechend der Tastaturfolge ausgelöst. Auch spezielle Umlaute und Unicode Zeichne lassen sich so erzeugen.

Es ist gar nicht so schwer es richtig zu machen. :mrgreen:

C++MFCProgrammierenWindows APIMartin Richter - Mo 24 Dez 2007 09:33

Neulich im http://www.c-plusplus.de/forum/ fand sich folgende Empfehlung:

XYZ schrieb:
Schreibe knapp vor der MAIN MESSAGE LOOP:
C/C++ Code:

PostMessage(DeinHandle,WM_CREATE,NULL,NULL); //oder so ähnlich vielleicht noch mit Instanz
while //MAIN MESSAGE LOOP

Ich staunte nicht schlecht. Grund für diesen Holzhammer war, dass „angeblich“ die Nachricht WM_CREATE, von Windows nicht verschickt wird. Also helfen wir einfach etwas nach und versenden diese selber.
Das sich dieser Code verbietet ist klar und ich will hier gar nicht weiter darauf eingehen warum.

Aber es bringt mich zum Thema:
Es ist eine beliebte Unsitte Windows Nachrichten, die nur für das interne Zusammenspiel der Komponenten und als „Benachrichtigungen“ dienen, selber zu versenden.
Favoriten hier sind oft WM_SETFOCUS, WM_PAINT, WM_SIZE, WM_KEYDOWN und manche andere.

Und Windows macht es dem Programmierer nicht einfach. In den Anfangszeiten von Windows wurde zu wenig bei der Namensgebung darauf geachtet, welche Nachrichten mehr oder weniger Aktionen auslösen und Eigenschaften setzen (z.B. WM_SETFONT) und welche Nachrichten als Notifications des OS dienen einem Programm mitzuteilen, dass etwas passiert ist, oder geschehen soll (z.B. WM_CREATE, WM_PAINT, WM_SETFOCUS, WM_SIZE, WM_ACTIVATE etc.).
Vieles wäre einfacher, wenn man Anhand des Nachrichtenamens erkennen würde, dass diese Nachricht vom OS versendet wird und damit nicht für den Eigenbedarf bestimmt ist.
Die letzte Gruppe der Nachrichten steht hier oft in einem direkten Zusammenhang mit einer API-Funktion (CreateWindow, UpdateWindow, SetFocus, SetWindowPos, SetActivteWindow etc.). Mit in diese Kategorie fällt, der Versuch mit WM_KEY… Nachrichten Tastatureingaben zu simulieren was auch nur in Ausnahmefällen korrekt funktioniert.

Mit der Einführung der neuen Common Controls, wurde hier nachgebessert. Sicherlich auch weil WM_COMMAND als Benachrichtigungs Medium etwas schmalbrüstig ist. WM_NOTIFY wurde eingeführt
Dieser Schritt war gut.  Interessanter Weise habe ich noch niemals den Versuch gesehen solche Notifications zu simulieren. Obwohl dies hier auch kein Problem wäre, denn es gibt hier keine API Funktionen, die Konfliktpotential anbieten würden.

Häufigster Grund solch einer Versuchung nachzugeben eine Nachricht selbst zu versenden, ist oft einfach Unwissen über die Windows-API. Und Eingangs erwähnte ich es schon: Die Dokumentation ist oft genug nicht eindeutig und macht zu selten auch Hinweise auf die entsprechenden verbundenen API Funktionen, siehe Doku zu WM_SETFOCUS in der wir nichts zu SetFocus lesen, außer in der Fußnote für See also. Erst die Doku der SetFocus API-Funktion gibt Aufschluss, dass diese Nachricht durch SetFocus versendet wirdund damit in gewisser Weise intern ist. Windows führt Buch welches Fenster den Fokus hat, aber dieser wird eben durch SetFocus gesetzt und nicht durch WM_SETFOCUS!

Fazit: Verurteilen kann man diese Unsitte schwer, außer mit dem Hinweis: „Es wurde nicht korrekt in der Doku gelesen, RTFM (Read the fine MSDN)“. Allerdings ist für Anfänger die MSDN oft genug einfach nur mehr erschlagend als informativ.
Dennoch kommt man nicht umhin sorgfältig auf die Zusammenhänge von Nachrichten und API-Funktionen zu studieren. Das bedeutet in diesem Zusammenhang, gerade die Dokumentationen der Funktionen, Nachrichten und Methoden zu lesen, die in der Fußnote unter See also aufgeführt werden. Oft genug findet man auf diesem Weg die Zusammenhänge (oft etwas mühsam) heraus.

Anmerkung: Die Dokumentation der MFC mach das Ganze leider noch schlimmer, denn hier werden nur kurze Auszüge der Windows-API wiedergegeben, die leider oft genug nicht detailliert genug sind. Hier ist immer angeraten, hinter einer virtuellen On… Funktion einen Windows Nachrichten Handler zu vermuten und in der Windows API noch einmal genauer nachzulesen. Allerdings macht sie auch einiges wiederklarer, denn sie kapselt die WM_… Nachrichten, die als Methoden/Getter/Setter funktionieren direkt in eigenständigen Memberfunktionen. Aber auch hier empfiehlt es sich nachzusehen was unter See also steht.

C++MFCProgrammierenWindows APIMartin Richter - Di 18 Dez 2007 18:11

Das viele Programmierer nur an die Maus denken, wenn Sie Kontextmenü hören ist schon an meinem letzten Blog Artikel klar geworden. 

Dennoch überraschen mich immer wieder Programm, die zwar korrekt auf die Kontextmenü-Taste (bzw. Umschalt+F10) reagieren, aber dann ein Kontextmenü links oben in der Ecke meines Monitors aufklappen.

OK! Es wurde verstanden, dass es eine WM_CONTEXTMENU Nachricht gibt, dass aber intelligentes Handling für Tastaturbenutzer anders aussieht, als für einen Mausbenutzer wird sehr selten verstanden oder berücksichtigt.

❗ Nehmen wir mal ein Beispiel:
Gegeben sei ein List View Control. Der Benutzer hat den Fokus auf diesem Control und drückt nun die Kontextmenütaste bzw. die Tasten Umschalt+F10.

❓ Frage: Was währe nun ein angemessenes Verhalten?

Wer die Doku gelesen hat wird wissen, dass in diesem Fall als Koordinaten (-1,-1) mit der WM_CONTEXTMENU Nachricht übermittelt werden. Dies ist nun der Grund dafür, dass viele Kontextmenüs nun in der linken oberen Ecke aufklappen, weil viele Entwickler eben nicht lesen (können), oder sich keine Gedanken machen, dass es einen Unterschied macht Mausbenutzer zu sein oder Tastaturbenutzer.

(-1,-1) ist keine geeignete Position für das Kontextmenü!

Nun die Mausposition zu bestimmen, wäre meiner Meinung nach, auch nicht angemessen. Denn der User benutzt diese ja gar nicht, und hat sie evtl. sogar einfach an den Bildschirmrand geschoben. Das Kontextmenü dort aufpoppen zu lassen wäre genauso falsch.

Einzig richtig wäre es den aktuell selektierten Eintrag mit dem Fokus im List View zu bestimmen und das Popupmenü knapp darunter und leicht rechts versetzt davon anzuzeigen. Dem Benutzer wäre der Kontextbezug sofort klar und auch seine Augen müssten nicht erst an eine andere Bildschirmposition wandern um zu erfassen, was nun möglich ist

Im Klartext heißt das für den Entwickler: selektiertes Item suchen (LVM_GETNEXTITEM), dessen Bildschirmposition Position zu bestimmen (LVM_GETITEMRECT) und sich eine gute alternative zu überlegen, wenn das Item außerhalb des sichtbaren Bereiches ist (z.B. Mitte des Controls).

Fazit: Um Kontextmenüs auch mit der Tastatur bedienbar zu machen gehört etwas mehr Grips und Aufwand dazu, als nur eine Popupmenü an einer bestimmten Koordinate anzuzeigen.

C++MFCProgrammierenWindows APIMartin Richter - Sa 15 Dez 2007 14:56

Ich freue mich jedesmal, wenn ich ein Programm benutze und ein Kontextmenü öffnen will und es öffnet sich nicht.

„Ja da hast Du wohl nicht mit der rechten Maustaste geklickt, sonst würde es sich öffnen!“

werden jetzt einige sagen.
Und ja es stimmt, ich habe nicht mit der rechten Maustaste irgendwohin geklickt sondern die Kontextmenü-Taste rechts unten auf meiner Tastatur benutzt. Und wer es noch nicht wusste Umschalt+F10 löst die selbe Funktion aus.

Falls es also noch jemand nicht bemerkt haben sollte. Microsoft hat in der Windows API direkt eine Nachricht nur für die Behandlung von Kontextmenüs reserviert und die heißt: WM_CONTEXTMENU! Und es sollte jedem Entwickler bereits in Fleisch und Blut übergegangen sein, diese Nachricht und nichts anderes für Kontextmenüs zu verwenden.

Der nette Nebenbonus dieser Nachricht, ist, dass man sich nicht um jedes Fenster alleine kümmern muss. WM_CONTEXTMENU wird an das Elternfenster weitergereicht, wenn ein Kindfenster diese Nachricht nicht behandelt. Das macht es auch einfach für Dialoge einen zentralen Handler zu bauen.

C++MFCProgrammierenWindows APIMartin Richter - Mi 12 Dez 2007 19:35

Carsten hat mich dazu inspiriert noch ein wenig mehr Don Quichotte zu spielen und gegen Windmühlen zu kämpfen: Damit hier mein zweiter Beitrag zum Thema Unsitten.

Der hInstance Parameter in CreateWindow(Ex) und RegisterClass (WNDCLASS) wird oft genug nicht verstanden. Man braucht für diese Funktionen einen HINSTANCE Wert. Den hat aber niemand in der Tasche, wenn man mit der Win32 API pur mal eben so ein Programm schreibt. Die wenigsten kommen auf die Idee den hInstance Wert aus WinMain und DllMain irgendwo global zu speichern und zu verwenden. Globals sind ja irgendwie „böse“, und nicht OOP-like… 😉

Was also tun? Irgendein Unwissender empfiehlt einfach GetModuleHandle(NULL) zu verwenden und seit dem dieser Unwissende diesen Tipp in die Welt gesetzt hat kursiert er durch die Foren… unaufhaltsam…

❗ Das Problem: Es geht… manchmal… aber portabel ist der Code damit nicht und man erlebt eigentümliche Sachen damit an anderer Stelle unter annderen (DLL-)Umständen. Aber warum?

Das Geheimnis steckt darin, dass Fensterklassen nicht einfach so unter ihrem Namen abgelegt werden, sondern unter einem Namen und der hInstance, die bei RegisterClass mit angegeben wird. So hat jeder Prozess seine Liste der Fensterklassen die aus hInstance und dem Namen der Klasse bestehen. Auch die Standard Fensterklassen wie Edit, Button, Static etc. werden mit der hInstance der User32.dll gespeichert.

Wird nun ein Fenster erzeugt mit CreateWindow(Ex), dann benutzt der Windowsmanager die Kombination aus hInstance Wert, der bei CreateWindow(Ex) angegeben wird und dem Namen der Klasse um den entsprechenden Klassen Eintrag zu finden.

Es ist also vollkommen OK wenn DLLs selber Klassen registrieren mit ein und unterschiedliche Module den selben Namen verwenden. Da gibt es keinen Konflikt, denn durch die hInstance Werte bleiben die Klassen eineindeutig. Und auch jede DLL kann damit, dass von Ihr gewünschte Fenster mit der entsprechenden Klasse erzeugen, denn jede DLL hat eine unterschiedliche hInstance.

Einzige Ausnahme ist eine Klasse, die mit dem Klassenstil CS_GLOBALCLASS registriert wird. Bei solch einer Klasse wird der Windowsmanager nur auf den Namen und nicht auf den Wert der hInstance achten. Jedem wird klar sein, dass die Standardfensterklassen der USER32.DLL und auch die Klassen der COMCTL32.DLL mit diesem Stil registriert wurden.
Und klar ist auch, dass man seine eigenen Klassen, die in DLLs liegen mit diesem Flag CS_GLOBALCLASS registrieren muss, wenn man diese Klassen applikationsweit verwenden will. Würde man also ein Dialogtemplate mit einer eigenen Klasse anlegen, die in einer DLL liegt, so kann der Dialogmanager, der wieder hInstance der Applikation verwendet, die entsprechende Klasse nicht finden, wenn diese nicht mit CS_GLOBALCLASS registriert wurde.

Es ist und bleibt eine Unsitte GetModuleHandle(NULL) beim Registrieren der Windowsklasse zu verwenden, denn dieses hInstance, das man erhält ist natürlich das hInstance der Applikation, und nicht das des Modules, welches die Klasse enthält, z.B. eben eine DLL. Es wundert nicht wenn man etwas später seine Klassen in eine DLL auslagert erstaunt feststellt, dass auf einmal die Sachen nicht mehr funktionieren wie sie sollen.

Um solchen Problemen aus dem Weg zu gehen sollte man immer das hInstance verwenden, das zu dem entsprechenden Modul gehört. Diesen Wert erhält man durch WinMain oder DllMain. Am Besten man speichert diesen in einer globalen Variablen. Die MFC hat hierzu eine spezielle Funktion AfxGetInstanceHandle, die dies für das entsprechende Modul erledigt. Aber ein MFC Programmierer würde ja auch AfxRegisterClass verwenden und nicht in diese Probleme laufen, wiederum vorausgesetzt er verwendet auch brav AFX_MANAGE_STATE 🙂

❗ Anmerkung: Es gibt auch Code, der einem erlaubt das hInstance Handle zu einem Code Abschnitt zu bestimmen.
http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx (sofern man einen MS-Linker verwendet)

Auch der folgende etwas trickreichere Code ist einen Blick wert:

HINSTANCE Get_hInstance()
{
 MEMORY_BASIC_INFORMATION mbi; 
 VirtualQuery(Get_hInstance, &mbi, sizeof(mbi)); 
 return reinterpret_cast<HINSTANCE>(mbi.AllocationBase); 
}

Aber bitte diesen Code nicht in eine DLL auslagern :mrgreen:

Nächste Seite »