Die Unsitte Tastatureingaben mit WM_KEYDOWN Nachrichten zu simulieren

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:

Die Unsitte Windows interne Nachrichten zu versenden

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.

Die Unsitte in WM_CONTEXTMENU keine Tastaturnutzer zu berücksichtigen

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.

Die Unsitte WM_RBUTTONDOWN statt WM_CONTEXTMENU zu verwenden

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.

Die Unsitte immer GetModuleHandle(NULL) für hInstance in CreateWindow und RegisterClass zu verwenden

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:

ATE beim „Ready for take off“ in Frankfurt vom 19.-21.02.2008

Wer mich mal persönlich kennen lernen will, der kann mich in Frankfurt beim großen Launch “Ready for take off” von Windows Server 2008, SQL Server 2008 und Visual-Studio 2008 vom 19.-21. Februar 2008 treffen.

Ich werde einer der vielen MVPs sein, die als ATEs (Ask the Experts) die Veranstaltung begleiten.

Weitere Infos zur MFC in VS-2008 und der BCG Pro-Library

Hier noch ein paar Infos zu dem Thema:

Eine Microsoft Bekanntmachung auf den US VC++ Seiten: MFC Update Powered By BCGSoft
http://msdn2.microsoft.com/de-de/visualc/bb892882.aspx

Eine Pressemitteilung von BCG-Soft vom 09.11.2007:
http://www.bcgsoft.com/pressreleases/PR071110.pdf

Fragen und Antworten im BCG-Forum:
http://www.bcgsoft.com/cgi-bin/forum/topic.asp?TOPIC_ID=4476

Intellisense Hotfix für VS-2005 ist verfügbar

Soeben wurde ein Hotfix veröffentlicht der einige Performance Probleme mit dem Intellisense in Visual-Studio 2005 beheben soll.

Infos über diesen Hotfix gibt es im Visual C++ Team Blog.
Weitere Infos und die Liste der betroffenen Dateien findet man auch in in diesem KB-Artikel 943969

Hier der aktuelle Download Link für die englische Version.
Ob andere Sprachversionen (Deutsch) verfügbar sein werden wurde bereits bei der Produktgruppe intern nachgefragt.

Wichtig ❗ SP1 für VS-2005 muss installiert sein für diesen Hotfix.

Anmerkung: Nutzer von VisualAssist von http://www.wholetomato.com  bemerken von diesen Problemen kaum etwas.

Microsoft und sein Bekenntnis zu C++

Eigentlich ist es ja schön, dass Microsoft sich endlich mal zu etwas mehr durchgerungen hat, als nur partieller Kosmetik im Bereich der nativen Softwareentwicklung mit C++.

Nach meiner Meinung kommt das ganze allerdings um Jahre zu spät! Ich hätte spätestens mit VS-2005 erwartet, dass Microsoft den verbalen Bekenntnissen, dass es weiterhin auch native Entwicklung mit C++ geben wird, auch Taten folgen lässt.

Microsoft hat sich mit der einfältigen Umbenennung von Visual Studio in ein VS.NET 2002 keinen Gefallen getan. Die Verunsicherung unter den vielen C++ Entwicklern durfte ich damals in Neuss beim großen Launch des VS.NET 2002 als ATE (Ask the Expert) am eigenen Leib erfahren.

Geändert hat sich nicht viel in den Jahren danach. Die Verunsicherung ist geblieben und Vertrauen wurde keines zurück gewonnen. Ein vorbehaltloses Prüfen der neuen Compiler und Visual Studio Versionen hat in vielen Firmen nicht stattgefunden. Die Änderungen waren gravierend sicherlich, wenn man den Compiler und die IDE von VC++ 6.0 mit der von VC++ 2005 vergleicht, da liegen einfach Welten dazwischen.

Irgendwie hatten zu viele Firmen und Entwickler das Gefühl „nun stirbt C++“, „jetzt  ist die vielfach totgesagte MFC wirklich tot“. Zuversicht in die bestehende Technologie zu setzen war einfach nicht da.

Das Micrsoft natives C++ nicht abschreibt wurde zwar ab und zu gesagt, aber das hörte sich eher wie Pfeifen im dunklen Wald an. Die ganze Situation machte es C# und .NET Entwicklern leicht auf C++ Entwicklern herumzuhaken und sie zutiefst zu bemitleiden (natürlich immer mit einem hämischen Grinsen). Und teilweise geschah dies sogar aus den eigenen Reihen von Microsoft. Hatte man auf einmal mit C++ keine zukunftsweisende Technologie mehr?
Die Tracks bei Kongressen füllten sich mit .NET Beiträgen. Und was sich in C++ getan hat (und es hat sich noch einiges getan) wurde kaum veröffentlicht oder nicht gehört. Auch was Dokumentationen betrifft, hat sich die C++ Gruppe nicht mit Ruhm bekleckert.

Noch „besser“ wurde es als schwachsinnige Aussagen die Runde machte, dass die nächsten Betriebssysteme nur noch .NET erlauben würden, und demnächst alles, bis zum Treiber mit .NET entwicklet würden. C++ braucht man nicht mehr.
Schaut man sich heute an, wieviel .NET in Vista und Windows 2008 Server steckt, dann merkt man, wie blödsinnig diese Annahmen waren und das es kaum Ersatz gibt für native Entwicklung in C++.
Fakt ist: bis heute wurde keine wirklich weitreichende und wichtige Schnittstelle in Win32 geschaffen die nur aus der managed Welt anzusprechen wäre. Und selbst wenn, so ist die Brücke aus C++ in die managed Welt ist kurz…

Warum wurden keine Walkthroughs und Dokumentationen geliefert , die einem bei der Umstellung von Code der unter VC6 entwickelt wurde auf VC++2003/2005 erklären und leichter machen? Walkthroughs, die gezielt auf die speziellen Warnungen eingehen, die einem um die Ohren fliegen, oder konstruktiv bei Problemen mit dem Umstieg helfen, finden wir heute höchstens in der Community entsprechende Beiträge.

Wäre es zu viel verlangt gewesen, zum Beispiel sofort bei der Umstellung eines VC6 Projektes bestimmte Projekteinstellungen so zu gewährleisten, dass möglichst wenig Warnungen entstehen, aber der Programmierer auf die Änderungen, die seinen Code sicherer und kompatibler machen, klar hinweisen?

Es ist verschlafen worden! Ja es wurde Jahre lang geschlafen. MFCnext ist schön, nett. Das TR1 kommt ist prima. Ich frage mich aber ob es wirklich genügt das verloren gegangene Vertrauen wieder zurück zu gewinnen?

Just my 2 cents…