Überwachung von Tastatureingaben

Einleitung

Oft wird in Beiträgen aus Foren und Newsgroups die Frage gestellt, wie man systemweit Tastatureingaben aufgezeichnet bzw. auf Eingaben, die nicht unbedingt im eigenen Fenster getätigt werden, reagieren kann. Vielfach werden solche Beiträge ignoriert, da die Leser annehmen, daß der Schreiber ein Programm entwickeln will, um die Tastatur zu „überwachen“. Doch zum Ausspionieren von Tastatureingaben gibt es bereits ausreichend kostenlos erhältliche Programme im Internet, was zur Annahme veranlaßt, daß die Überwachung nicht nur zu diesem Zweck benötigt wird.

Um eine systemweite Überwachung der Tastatur zu realisieren, gibt es zwei Ansätze. Einerseits kann mit einem Timer-Steuerelements alle n Millisekunden der Status aller Tasten abgefragt werden, auf der anderen Seite besteht die Möglichkeit, über einen Tastaturhook Meldungen beim Drücken einer Taste an die Visual-Basic-Anwendung weiterzuleiten. Während die erste Lösungsmöglichkeit einfach zu implementieren ist, muß bei der zweiten Möglichkeit auf eine zusätzliche Programmiersprache zurückgegriffen werden.

Tastaturüberwachung mit einem Timer-Steuerelement

Da es mit reinen Visual-Basic-Mitteln nicht möglich ist, einen systemweiten Hook zu setzen, wollen wir in unserem Beispiel der Status jeder Taste in bestimmten zeitlichen Abständen überprüfen. Zu diesem Zweck stellt das Windows-API die Funktionen GetAsyncKeyState und GetKeyState bereit. Diese Funktionen werden über einen Zeitgeber, dessen Intervall auf eine Millisekunde eingestellt ist, aufgerufen. Zwischen den beiden Funktionen gibt es Unterschiede, so gibt GetAsyncKeyState sowohl den Status der Taste als auch Informationen darüber zurück, ob sich der Tastenstatus seit dem letzten Aufruf der Funktion für die Taste verändert hat.

Folgendes Listing zeigt die Deklarationen der Funktionen GetAsyncKeyState und GetKeyState. Über die Konstante VK_CAPITAL kann ermittelt werden, ob ein Groß- oder Kleinbuchstabe vorliegt:

Private Declare Function GetKeyState Lib "user32.dll" ( _
    ByVal nVirtKey As Long _
) As Integer

Private Declare Function GetAsyncKeyState Lib "user32.dll" ( _
    ByVal vKey As Long _
) As Integer

Private Const VK_MENU As Long = &H12&
Private Const VK_SHIFT As Long = &H10&
Private Const VK_CONTROL As Long = &H11&
Private Const VK_CAPITAL As Long = &H14&

' usw., auch VB-eigene Konstanten ('vbKeyF1', 'vbKeyA' etc.).
Deklarationen der verwendeten Funktionen.

Will man lediglich prüfen, ob gerade eine Taste gedrückt ist, wobei es irrelevant ist, ob sich seit der letzten Überprüfung der Status der Taste geändert hat, dann muß nur überprüft werden, ob das höchstwertige Bit (MSB, Test mit „Verunden“ mit −32.768) gesetzt ist:


' Prüfen, ob die Alt-Taste gedrückt ist.
If CBool(GetAsyncKeyState(VK_MENU) And &H8000) Then
    Call MsgBox("Die Alt-Taste ist gedrückt.")
Else
    Call MsgBox("Die Alt-Taste ist nicht gedrückt.")
End If
Prüfen, ob eine Taste seit der letzten Überprüfung gedrückt wurde oder gedrückt ist.

Bei einigen Projekten zur Tastaturüberwachung, die im Internet als Quellcode verfügbar sind, tritt das Problem auf, daß beim initialisieren des Aufzeichnungsvorhangs, also wenn die oben genannten Funktionen für eine Taste zum ersten Mal aufgerufen werden, ermittelt wird, daß diese gedrückt ist, obwohl dies nicht der Fall ist. Dies liegt daran, daß der Tastaturpuffer vor dem ersten Durchlauf der Tasten nicht leer ist. Damit die Anwendung korrekt funktioniert, muß zuerst der Status jeder Taste abgefragt und erst danach mit der Aufzeichnung begonnen werden. Wenn die Taste gedrückt ist, ist das höchstwertige Bit des Rückgabewertes gesetzt:


' Prüfen, ob die Alt-Taste gedrückt ist.
If CBool(GetAsyncKeyState(VK_MENU) And &H1) Then
    Call MsgBox("Die Alt-Taste ist gedrückt.")
Else
    Call MsgBox("Die Alt-Taste ist nicht gedrückt.")
End If
Prüfen, ob eine Taste gedrückt ist.

Bei jedem Feuern des Timer-Steuerelements wird für alle interessanten Tasten eine solche Überprüfung vorgenommen. Bei einigen Tasten ist es für das Protokoll interessant zu wissen, ob die Hochstelltaste gedrückt ist, um zwischen Groß- und Kleinbuchstaben zu unterscheiden. Im Parameter von GetAsyncKeyState muß für die Taste der virtuelle Tastencode (engl.: virtual key code“) übergeben werden. Dazu kann man entweder eine der VK_*-Konstanten benutzen oder die Visual-Basic-eigenen vbKey*-Konstanten angeben.

Bei der Anfertigung eines Tastaturprotokolls im Textformat ist zu beachten, daß sprachspezifische Sonderzeichen (Tasten), beispielsweise Ä, im Code gesondert behandelt werden müssen. Weiters können für Funktionstasten eigene beschreibende Texte im das Protokoll angegeben werden.

Tastaturüberwachung mit einem Tastaturhook

Einen etwas fortgeschrittenen Ansatz zum Implementieren einer Tastaturüberwachung bietet ein extra dafür vorgesehener Hook. Ein Hook ist nichts anderes als eine Rückrufprozedur, die aufgerufen wird, wenn ein bestimmtes Ereignis, in unserem Fall ein Tastendruck, eintritt. Windows stellt Funktionen zum Setzen eines solchen Hooks und zur weiteren Verarbeitung bereit. Alles, was der Programmierer machen muß, ist die Bereitstellung einer passenden Rückrufprozedur, deren Aussehen vorgegeben ist. Bei Eintreten des Ereignisses, für das der Hook hinzugefügt wurde, ruft Windows nacheinander die in die Kette von Rückrufprozeduren eingehängten Prozeduren auf.

Leider unterliegen manche Hooks, darunter auch systemweite Tastaturhooks, einer Einschränkung, die eine reine Visual-Basic-Lösung verhindert. Die Rückrufprozedur muß nämlich in einer „echten“ DLL implementiert werden, etwas, das man mit Visual Basic nicht erstellen kann. Normalerweise wird man deshalb auf Visual C++ zurückgreifen, was für das zu diesem Artikel gehörende Beispiel auch getan wurde. Selbstverständlich kann man aber auch jede beliebige andere Programmiersprache benutzen, für die ein Compiler verfügbar ist, der Win32-DLLs kompilieren kann.

Die Bibliothek, in der der Hook implementiert ist, muß laut Spezifikation zwei Funktionen offen legen, nämlich eine, die den Hook setzt und eine, die den Hook wieder entfernt. Allerdings suchen wir nicht nach einer reinen C-Lösung, sondern wollen, daß eine in Visual Basic geschriebene Anwendung über die Tastaturereignisse informiert wird. Zu diesem Zweck verfügt die von der DLL exportierte Funktion zum Setzen des Hooks über einen Parameter, in dem eine Fensterzugriffsnummer übergeben werden kann. Die Bibliothek leitet dann innerhalb der Rückrufprozedur die Nachrichten an die Fensterprozedur des in Form der Zugriffsnummer übergebenen Fensters weiter. Innerhalb der Fensterprozedur eines Formulars der Visual-Basic-Anwendung muß demnach nur mehr nach der Nachricht WM_COPYDATA Ausschau gehalten werden.

Der einfache Tastaturhook

Das Setzen dieses Hooks geschieht über einen Aufruf der API-Funktion SetWindowsHookEx mit der Konstanten WH_KEYBOARD als Parameterwert für idHook. Als Rückrufprozedur wird ein Funktionszeiger auf eine in der DLL benutzerdefinierte Funktion, die den Prototypen KeyboardProc implementiert, angegeben. Innerhalb der Rückrufprozedur wird eine Struktur des Typs COPYDATASTRUCT mit den Informationen zum eingetretenen Tastaturereignis gefüllt und eine WM_COPYDATA-Nachricht an die Fensterprozedur eines beliebigen Fensters gesendet. Innerhalb der Fensterprozedur können die Nachricht und die dazugehörigen Informationen verarbeitet werden.

Meherere Anwendungen können jeweils ihren eigenen Tastaturhook installieren, weshalb es innerhalb von Windows eine ganze Kette von benutzerdefinierten Rückrufprozeduren geben kann, die auf die Nachricht reagieren. Aus diesem Grund ist es erforderlich, am Ende der Rückrufprozedur die Funktion CallNextHookEx aufzurufen, damit andere Anwendungen, die in der Aufrufkette weiter hinten gereiht sind, die Benachrichtigung erhalten.

Low-Level-Tastaturhooks

Seit Windows NT 4.0 besteht die Möglichkeit, einen Low-Level-Tastaturhook zu verwenden. Auch dieser muß in einer „echten“ DLL implementiert werden, was eine reine Visual-Basic-Lösung unmöglich macht. Die Installation eines solchen Hooks wird durch Aufrufen der API-Funktion SetWindowsHookEx mit der Konstanten WH_KEYBOARD_LL als Wert des Parameters idHook durchgeführt. Die DLL muß eine Rückrufprozedur des Prototypen LowLevelKeyboardProc implementieren, in die von Windows u. a. ein Zeiger auf eine Struktur des Typs KBDLLHOOKSTRUCT hineingereicht wird. Durch Dereferenzieren des Zeigers gelangt man an die erweiterten Informationen zum Tastendruck.

Schlußwort

Es gibt mehrere Möglichkeiten, die Tastatur zu überwachen, von denen jede ihre Vor- und Nachteile aufweist. Will man eine Lösung, die ohne zusätzliche, nicht in Visual Basic geschriebene Komponenten funktioniert, dann muß man sich für die hier beschriebene Variante mit dem Timer-Steuerelement entscheiden. Wenn man noch zusätzliche Informationen zu den Tastaturereignissen ermitteln will, dann reicht ein einfacher Tastaturhook, etwa in einer C++-DLL implementiert, aus. Soll das Programm in der Lage sein, Tastaturereignisse zu unterdrücken bzw. zu verändern, dann wird man auch nicht um die Verwendung eines Tastaturhooks herumkommen.

Downloads

Beispielprojekt (KeyboardHook.zip)

Projekte im Visual-Basic-6.0- und Visual-C++-2003-Format.