Die Unsitte GetAsyncKeyState statt GeyKeyState zu verwenden…

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…

23 Gedanken zu „Die Unsitte GetAsyncKeyState statt GeyKeyState zu verwenden…“

  1. Hallo Martin,
    also zuerst einmal Gratulation zu deinem blog. Schön gemacht und informativ.

    Leider bin ich mit deinem Artikel über GetAsyncKeystate nicht einverstanden 😉

    Denn in der Tat gibt GetAsynchKeyState den aktuellen Zustand der Taste zurück. Und zwar in dem es den Status des Keyboardtreibers ermittelt.
    Das lernt man aus der Remarks Sektion zu GetKeyState
    „The key status returned from this function changes as a thread reads key messages from its message queue. The status does not reflect the interrupt-level state associated with the hardware. Use the GetAsyncKeyState function to retrieve that information. “

    Warum eine hohe Prozessorauslastung dazu führen soll das GetAsynchKeyState zu falschen Ergebnissen kommt ist mir schleierhaft. Es führt höchstens zu einer längeren Ausführungszeit des Aufrufs.

    Eigentlich fällt mir sofort ein vernünftiger Grund ein GetAsynchKeyState zu verwenden! 😉 Und zwar wenn man wissen will ob gerade im Moment eine Taste gedrückt wird und wenn ja welche.

    Du schreibst auch:
    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!

    Naja aber es muss nicht zwangsläufig eine WM_KEYDOWN für eine Taste wie Shift oder Ctrl etc. in deinem Programm ankommen, der benutzer kann z.b Ctrl drücken, dann mit Tab auf deine App wechseln und dann C drücken, um aus dem aktuellen edit feld deiner app etwas ins Clipboard zu kopieren.

    Gruß Gunnar

  2. Eben! GetAsynchKeySate liefert den aktuellen Zustand und nicht den Zustand zur der Zeit zu der die Nachricht erzeugt wurde, die aktuelle behandelt wird. Das macht eben nur GetKeyState ❗

    Vielleicht habe ich mich nicht klar ausgedrückt, also hier ein Beispiel:
    1. Benutzer drückt auf einer Markierung Strg+C
    2. Keydown Nachricht wird in Queue eingestellt. Das OS ist aber gerade schwer mit einem Defrag oder sonst was beschäftigt.
    3. Bevor die Nachricht vom Programm abgearbeitet wird (aber vielleicht noch innerhalb von 3/10sec). hat der User die Taste Strg, schon wieder losgelassen.

    Was passiert (lassen wir Accelerator mal außen vor):
    – WM_KEYDOWN wird empfangen mit dem ‚c‘.
    Möglichkeit 1: Programm verwendet GetAsnychKeystate und reagiert falsch, denn diese Funktion meldet „Strg-Taste nicht gedrückt“
    Möglichkeit 2: Programm verwendet GetKeyState und reagiert korrekt. Zu der Zeit zu der die Nachricht erzeugt wurde, war die Taste Strg gedrückt!

    Das ist eben das Problem, das bei hoher OS Auslastung Nachrichten eben nicht zeitnah bearbeitet werden!
    Stell Dir das ganze mal vor mit einer verzögert bearbeiteten Maus-Drag&Drap-Aktion, denn auch diese landen in der Queue und werden verzögert abgearbeitet. Und es macht einen gewaltigen Unterschied ob Strg gedrückt ist oder nicht.

    Ich bleibe dabei!
    1. GetAsynchKeyState ist in einem Messagehandler immer falsch!
    2. GetAsynchKeyState macht keinen Sinn, ich sehe keine vernünftige Anwendung!
    3. Ich bleibe auch dabei das 80% der fragenden Newbies noch nicht verstanden haben, dass Tastatureingaben Nachrichten erzeugen und dennoch auf GetAsnychKeyState hingewiesen werden.

  3. Vielleicht habe ich mich nicht klar ausgedrückt. Nicht einverstanden heisst nicht dass der Artikel ganz falsch wäre. Zum grössten Teil ist er ja richtig, wenn auch teilweise missverständlich, aber das hast du ja im Kommentar gut erklärt.

    Du hast recht mit :
    GetAsynchKeyState ist in einem Messagehandler immer falsch!
    aber nicht Recht mit :
    GetAsynchKeyState macht keinen Sinn 😉
    denn der Sinn ergibt sich aus deiner ersten Behauptung.
    GetAsynchKeyState macht Sinn, wenn man den key state ermitteln will und sich nicht in einem key msg handler befindet.

    Eine Anwendung aus meiner Praxis: ein remote control server teilt das Bedienrecht auf zwischen lokal und remote, solange eine Taste gedrückt ist darf das Recht nicht wechseln.

    Gruß Gunnar

  4. Ich finde die Verwendung von GetAsyncKeyState beispielsweise in folgendem Fall sinnvoll:

    int main()
    {
    	while( !(GetAsyncKeyState(VK_ESCAPE) & 0x8000) ){
    		// Solange die Schleife durchlaufen
    		// bis ESCAPE gedrückt ist
    	}
    	return 0;
    }
  5. @Gunnar: Das sind nicht nur Keyboard Nachrichten. Auch Mausnachrichten, sprich alle Inputnachrichten die nur mit GetKeyState verwendet werden dürfen!

    Ich würde immer GetKeyState verwenden. Zudem wechselt eben (wie Du selbst angemerkt hast) der Keystate nur wenn eine neue Nachricht ankommt!
    Aber OK. Ich denke wir meinen das selbe ;)…

  6. Somit stellt sich nun natürlich die Frage, wie man denn sonst eine Schleife solange durchlaufen kann, bis ESC gedrückt ist?
    Ich sehe da leider keine einfache Alternative, das zu tun.

  7. @Nobody
    Einfach nicht, aber möglich: 2. Thread mit MsgWaitForMultipleObjects(…) der bspw. ein Event (eigentlich reicht sogar ein volatile bool) setzt welcher wiederum in der Schleife der Hauptthreads abgefragt wird.

    Würde ich aber auch nicht so machen – wäre mir für diesen Fall zu aufwändig 😉

    Kein Scherz: Insbesondere wenn das Polling im Vergleich zur übrigen Arbeit in der Schleife sehr wenig Zeit benötigt. Das müsste mir erstmal ein Profiler zeigen… 😉

  8. Ja das ist es eben genau das, was ich mir auch gedacht hab.
    Deshalb nehme ich da schon lieber die simple Lösung mit GetAsyncKeyState() anstatt dass ich mir da irgendein ein komplexes Konstrukt zusammen bastle.
    Bisher hatte ich außerdem auch noch nie ein Problem mit dieser Funktion, hat eigentlich immer so gearbeitet und funktioniert, wie ich mir das vorgestellt hab.

  9. @Martin: bloß weil du keine vernünftige Anwendung für GetAsyncKeyState siehst, heißt das noch lange nicht, dass es nicht doch für einige Fälle sehr sinnvoll ist!

    z.B. wenn ein Programm im Hintergrund die Tastatureingaben überwachen soll – auch wenn das input queue nicht zu diesem Programm gesendet wurde, sondern zu einer anderen aktiven Anwendung.

    1. Du kannst nie sicher sein, dass Du in dem Moment in dem sich der Tastaturstatus ändert auch GetAsynchKeyState aufrufst. Zudem hast Du keine Info, wie oft eben eine Taste hintereiander gedrückt wurde, oder eben der Tastatur-Repeat ausgelöst wird. Wenn man das will benötigt man einen Hook!
      Man kann mit GetAsnchKeyState keine Tastatureingaben zu 100% überwachen, nicht mal zu 90%.
      Es gibt in meinen Augen keine vernünftige Anwendung… Jeder kann machen was er will.

  10. Ok, aber wie überwache ich dann Tastatureingaben global über ein im Hintergrund laufendes Programm? Mit GetKeyState dürfte das ja nicht gehen, wie hier beschrieben wird:
    http://blogs.msdn.com/oldnewthing/archive/2004/11/30/272262.aspx

    So gut wie alle Beispiele aus dem Netz, welche ich fand, basierten auf der GetAsyncKeyState-Variante.

    Über einen schnellen Timer bekommt man eigentlich 100% der Anschläge mit. Etwas problematischer ist es aber schon, Tastenkombis zu erkennen, wo die Tasten nacheinander gedrückt werden (also erst Strg und später kommt dann z.B. C hinzu).

    GetKeyboardState wäre eine weitere Möglichkeit, welche aber -gerade im Hinblick auf Kombis- auch so seine Tücken hat.

    Du sagst „Jeder kann machen was er will“ – Stimmt. Aber ich denke, „Jeder macht das, was er kennt“ trifft es noch besser. 😉

    Ich lasse mich gerne eines Besseren belehren, wenn du mir einen optimaleren Lösungsansatz aufzeigst. 🙂

    1. Das habe ich bereits geschrieben: Nimm einen Keyboard Hook! Das machen viele nur nicht, weil sie nicht die Energie aufbringen sich mit der etwas komplexeren Technik durch Lesen der MSDN anzufreunden. 😉
      Das ist die einzige 100% Methode. Du hast selbst die Probleme beschrieben: Wie glaubst Du denn, dass kombinierte Zeichen bei Dir erfasst werden wie z.B. á und é etc.?
      BTW: Der Artikel wurde nicht geschrieben um „globales Überwachen der Tastatur“ zu beschreiben oder zu diskutieren. Was Du hier ansprichst war nicht Thema! Ich denke, wir können solch eine Diskussion gerne in ein anderes Forum verlagern.

  11. > Dieser Code pollt und genau dass ist etwas was man immer vermeiden sollte!

    Nö.
    Polling ist meistens einfach zu implementieren.
    Polling ist meistens robust.
    Polling ist oft eine gute Lösung.

    Bevor ich 2x mehr Code schreibe nur um nicht zu pollen, oder mir stundenlang den Kopf zerbreche ob ich mir mit diversen Callbacks nen Deadlock basteln kann, pollo ich dann doch manchmal lieber.

    Muss man von Fall zu Fall entscheiden.

  12. Pingback: Anonymous
  13. spät aber trotzdem ein weiteres anwendungsbeispiel für asyncKeyState:

    die steuerung einer figur in einem spiel aus einem thread mit teilweise oder ständig höherer abarbeitungsrate als der ui-thread.

    bemerkt man beispielsweise im ego-shooter seiner wahl einen sich auf kollisionskurs befindlichen flugkörper, so ist es, vor allem wenn besagter shooter auf einem nicht wirklich grafikstarken rechner gespielt wird, nützlich, dass die physikengine, welche die IO’s in einer höheren rate „abtastet“ als der queue basierte ui-thread, die schnelle reaktionsfähigkeit der steuerung auf kosten einiger nicht dargestellten frames sicherstellt.

  14. Polling frisst CPU-Zeit und verhindert, daß die CPU sich schlafen legen kann oder CPU-Leistung in anderen Threads genutzt werden kann.

    Sowas hat in zeitgemäßem Code nichts verloren.

    Man kann es zum Ausprobieren mal verwenden. In produktiv eingesetzten Lösungen mMn ein „NO GO“!

    1. Hier geht es nicht um Polling.
      Hier geht es z.B. darum zum Beispiel festzustellen, ob eine bestimmte Taste während einer Mausaktion gedrückt ist. Das ist der häufigste Verwendungszweck für diese Funktion.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

This site uses Akismet to reduce spam. Learn how your comment data is processed.