Entwickler-Ecke

Windows API - Tastatur- und Maushook reagiert nicht systemweit


alias5000 - Mi 25.10.06 22:19
Titel: Tastatur- und Maushook reagiert nicht systemweit
Hi!

Ich habe mir, um festzustellen, ob (also nicht welche) in einer bestimmten Zeitspanne Eingaben via Tastatur oder Maus gemacht wurden, einen Tastatur- und einen Maushook geschrieben. Ich habe mich daran am Tutorial von http://www.dsdt.info orientiert.

Konkret geht es um die Implementierung eines automatischen Away- Modus in einem Chatprogramm.
Allerdings habe ich das Problem, dass mein folgender Code nur Nachrichten abfängt, wenn die Maus über einer Form der Anwendung ist, bzw. die Tastatureingabe an meine Anwendung geht (sie also den Fokus hat). Bewegt sich die Maus abseits einer Form der Anwendung, bzw. geht eine Tastatureingabe an ein anderes Programm als meins, so scheint der Hook gar nichts mitzubekommen. Zusätzlich habe ich das chronische Problem, ständig neu booten zu müssen (allerdings erst, wenn der Hook schon eine Weile läuft). Es scheint also ein Fehler in der Anwendung der Hooks zu sein und nicht, dass ich bloß die falschen Features oder so benutze ;)

Meine beiden Hooks erstelle ich so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
function StartAwayMonitoring(Hwnd: Cardinal; AlertProc: TAlertProc; Intervall: integer): Boolean; stdcall;
begin
  Result := False;
  if KBHHandle = 0 then begin //KBHHandle gehört zum Keyboardhook
    KBHHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyboardHookProc,
    HInstance, 0);
    //...
    Result := TRUE;
  end;
  if (MHHandle = 0and (result) then begin //das hier steht dann für den Maushook
    MHHandle := SetWindowsHookEx(WH_MOUSE, @MouseHookProc, HInstance, 0);
  end else begin
    Result := true;
  end;
//...
//anschließend wird nur noch ein nonVCL- Timer erstellt, um in Zeitintervallen vergleichen zu können. An dem liegt das Probloem mit dem "Fokus" aber nicht, habs bereits getestet
end;

Als Handle in StartAwayMonitoring übergebe ich in der aufrufenden Anwendung Application.Handle.


Den Code zum deinstallieren der Hooks lass ich mal weg, da er ja hier nicht von Relevanz sein wird.

Interessant könnte nich die HookProcedure sein. Ich habe hier mal die vom Maushook reingestellt, die vom Keyboard ist äquivalent aufgebaut.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
function MouseHookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  Result := CallNextHookEx(MHHandle, nCode, wParam, lParam); //Zur Erinnerung MHHandle ist das Handle des Maushooks
  if (nCode < 0then begin
    exit;
  end;
  //ab hier wird angenommen, dass eine Eingabe mit der Maus kam
  //...
end;



Ich hoffe ihr könnt damit was anfangen

Gruß alias5000


Delete - Mi 25.10.06 23:44

Liegt der Code auch in einer DLL?


Reinhard Kern - Do 26.10.06 03:18
Titel: Re: Tastatur- und Maushook reagiert nicht systemweit
user profile iconalias5000 hat folgendes geschrieben:
Hi!

Ich habe mir, um festzustellen, ob (also nicht welche) in einer bestimmten Zeitspanne Eingaben via Tastatur oder Maus gemacht wurden, einen Tastatur- und einen Maushook geschrieben. Ich habe mich daran am Tutorial von http://www.dsdt.info orientiert.

Konkret geht es um die Implementierung eines automatischen Away- Modus in einem Chatprogramm.
Allerdings habe ich das Problem, dass mein folgender Code nur Nachrichten abfängt, wenn die Maus über einer Form der Anwendung ist, bzw. die Tastatureingabe an meine Anwendung geht (sie also den Fokus hat). Bewegt sich die Maus abseits einer Form der Anwendung, bzw. geht eine Tastatureingabe an ein anderes Programm als meins, so scheint der Hook gar nichts mitzubekommen. Zusätzlich habe ich das chronische Problem, ständig neu booten zu müssen (allerdings erst, wenn der Hook schon eine Weile läuft). Es scheint also ein Fehler in der Anwendung der Hooks zu sein und nicht, dass ich bloß die falschen Features oder so benutze ;)
....
Interessant könnte nich die HookProcedure sein. Ich habe hier mal die vom Maushook reingestellt, die vom Keyboard ist äquivalent aufgebaut.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
function MouseHookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
  Result := CallNextHookEx(MHHandle, nCode, wParam, lParam); //Zur Erinnerung MHHandle ist das Handle des Maushooks
  if (nCode < 0then begin
    exit;
  end;
  //ab hier wird angenommen, dass eine Eingabe mit der Maus kam
  //...
end;



Ich hoffe ihr könnt damit was anfangen

Gruß alias5000


Hallo,

zu CallNextHookEx: geht irgendetwas in der Hook-Kette schief (auch Kollegen können Fehler machen, an dieser Stelle ganz besonders), kehrt dieser Call nie zurück, deine Bearbeitung kommt also nie zum Zug. Das würde auch die Fehler erklären: deine Hook-Routine wird immer wieder aufgerufen, kehrt aber nie zurück und bereinigt daher auch nie den Stack vom Aufruf.

Ich würde mal testhalber meine eigene Bearbeitung VOR CallNextHookEx ausführen oder CallNextHookEx ganz weglassen; Laut SDK ist CallNextHookEx optional, aber natürlich werden dann andere Hooks nicht mehr bedient, also ist das Weglassen zumindest unfein.

Gruss Reinhard


alias5000 - Do 26.10.06 15:37

(das ist jetzt der vierte Anlauf diesen Text zu schreiben, nachdem ich den PC dreimal lahmgelegt hatte :autsch: )

@Luckie: Danke. Ne, aber das ist schon in der DLL :wink:

@Reinhard:
Naja, das Problem liegt wanders. (Ich rede jetzt mal nur vom Maushook, aber der Tastaturhook verhält sich entsprechend)
Es ist vielmehr so, dass der Hook korrekt arbeitet (was das bekommen der Nachrichten betrifft), solange sich die Maus auf einer Form meiner Anwendung befindet. Wenn ich aber die Maus nicht mehr über einer Form meiner Anwendung bin, wird die HookProcedure nachweislich nicht mehr aufgerufen. (habs einfach mit Beep ausgeben lassen ;) ). Wenn ich das CallNextHookEx rauslasse, habe ich mit dem gesamten System ein ziemliches Problem, weil ich nix mehr steuern kann. Wenn ich das ans Ende setze, hab ich dasselbe Verhalten, wie wenns vorne dran steht. Also existiert kein Problem mit fremden Hooks.

Was mir noch spanisch vorkommt ist das ständige Abschmieren, meines Programms, wenn der Hook aktiv ist.
Zu dem Zweck hänge ich die entsprechende Hook- Unit mal unten an.

Kleine Funktionsbeschreibung dazu:
Es geht wie gesagt darum, einen automatischen Away-Modus in einem Chat zu realisieren. Dazu starte ich die "Überwachung", wie es mit der Eingabeaktivität (an Tastatur und Maus) aussieht mit StartAwayMonitoring. StopAwayMonitoring ist dann das entsprechende Gegenstück dazu. Zur Überwachung, ob man away ist, habe ich, um die DLL klein zu halten, einen nonVCL Timer integriert. Die TAlertProc ist die Prozedur, die dann aufgerufen wird, wenn sich der Away-Status verändert. Diese Procedure wird beim Aufruf von StartAwayMonitoring durch die Hauptanwendung an die DLL übergeben, so dass man das ganze eigentlich als ganz normales Event bezeichnen könnte.


Gruß alias5000


alias5000 - Do 26.10.06 16:08

Ok, ich habe eine neue (noch nicht fertige) Version von Asserbads Tutorial auf seiner Seite gefunden.http://assarbad.net/stuff/tutorials/hooks/hook_tutorial.pdf

Das Problem liegt wohl am verwendeten Hook. Es ist klar, dass ich außerhalb meiner Forms nix mitbekommen kann, wenn ich nur einen Thread-lokal Hook (WH_MOUSE, WH_KEYBOARD) verwende. Ich hab jetzt mal stattdessen WH_KEYBOARD_LL und WH_MOUSE_LL gesetzt (die systemglobal sind). Jetzt funktionierts. Anscheinend auch ohne Abstürze.
Jetzt hab ich halt noch das Problem, dass die (laut Tut) nur unter NT-Systemen funktionieren. Muss mich da mal erkundigen, wie ich das ändern kann, damit ich auch Win 98/ME unterstützen kann, ohne auf den Hook verzichten zu müssen.

Gruß alias5000


netspy - Do 21.12.06 01:36

user profile iconalias5000 hat folgendes geschrieben:
Das Problem liegt wohl am verwendeten Hook. Es ist klar, dass ich außerhalb meiner Forms nix mitbekommen kann, wenn ich nur einen Thread-lokal Hook (WH_MOUSE, WH_KEYBOARD) verwende. Ich hab jetzt mal stattdessen WH_KEYBOARD_LL und WH_MOUSE_LL gesetzt (die systemglobal sind). Jetzt funktionierts. Anscheinend auch ohne Abstürze.
Jetzt hab ich halt noch das Problem, dass die (laut Tut) nur unter NT-Systemen funktionieren. Muss mich da mal erkundigen, wie ich das ändern kann, damit ich auch Win 98/ME unterstützen kann, ohne auf den Hook verzichten zu müssen.

WH_MOUSE und WH_KEYBOARD sind schon richtig und funktionieren auch global. So stehts auch in der Win-API Doku:


Quelltext
1:
2:
WH_KEYBOARD  Thread or global
WH_MOUSE     Thread or global

Mario


alias5000 - Do 21.12.06 18:00

Und wie unterscheide ich das? Ich finde in meinem PSDK nix zu.. :roll:
Bisher habe ich das mit den Konstanten für NT-Systeme realisiert. Unter Win 9x funktioniert der Hook leider nur lokal auf diese Weise

Gruß alias5000


netspy - Do 21.12.06 18:02

user profile iconalias5000 hat folgendes geschrieben:
Und wie unterscheide ich das? Ich finde in meinem PSDK nix zu.. :roll:
Bisher habe ich das mit den Konstanten für NT-Systeme realisiert. Unter Win 9x funktioniert der Hook leider nur lokal auf diese Weise

Unter Wind 9x kann ich hier nicht mehr testen. Auf XP geht WH_MOUSE aber auf jeden Fall global.

Mario


alias5000 - Do 21.12.06 18:48

Ich hab das jetzt mal die Konstanten durch WH_MOUSE und WH_KEYBOARD ersetzt, aber unter XP bekomme ich damit nur einen lokalen Hook, so wie es im Tut. von Asserbad steht. Was unter Win98 passiert hab ich noch nicht getestet. Dort bekam ich mit WH_MOUSE_LL halt bisher immer einen lokalen Mousehook (und entsprechend Keyboard)

EDIT: unter Win98 ist der auch nur lokal, nicht global


netspy - Do 21.12.06 23:16

user profile iconalias5000 hat folgendes geschrieben:
Ich hab das jetzt mal die Konstanten durch WH_MOUSE und WH_KEYBOARD ersetzt, aber unter XP bekomme ich damit nur einen lokalen Hook, so wie es im Tut. von Asserbad steht.

Nein, das steht da nicht. Auf Seite 15 im PDF wird WH_MOUSE und WH_KEYBOARD für den globalen Hook gesetzt und das funktioniert auch so. Vielleicht hast du die Hook-Prozedur nicht in einer extra DLL. Das würde erklären, warum es bei dir nur lokal und nicht global funktiniert.


alias5000 - Do 21.12.06 23:31

Der Code ist der, der da oben steht. Das ganze habe ich schon in einer extra DLL, die statisch mit meiner Anwendung gelinkt wird.

Gruß alias5000


netspy - Do 21.12.06 23:43

Dann kann ich es leider auch nicht erklären. WH_MOUSE und WH_KEYBOARD sind auf jeden Fall global, so stehts in der API-Doku, bei Asserbad und so funktioniert es auch bei mir unter XP.


alias5000 - Do 21.12.06 23:46

Könntest du evtl. deinen Code nochmal posten, wenn du sagst, dass es bei dir geht?
Vllcht gibts doch irgendwie, irgendwo einen entscheidenden Unterschied...
(oder auch per PN)

Wäre sehr nett von dir
Gruß alias5000


Raffo - Fr 22.12.06 06:46
Titel: Re: Tastatur- und Maushook reagiert nicht systemweit
user profile iconalias5000 hat folgendes geschrieben:
Konkret geht es um die Implementierung eines automatischen Away- Modus in einem Chatprogramm.


siehe hier http://www.delphi-forum.de/viewtopic.php?t=66512&highlight=idle very simple, ohne DLL, ohne Hook.

Könnte man auch ohne Funktionsaufruf implementieren, Timer "idleTimer" auf TForm setzen (Interval z.B. 5 Sek.)
und ein Label "idleTimeLabel", ach ja, das Ganze funzt erst ab D5


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
procedure TForm1.idleTimerTimer(Sender: TObject);
var
   d:longint;
   LInput: TLastInputInfo;
begin

LInput.cbSize := SizeOf(TLastInputInfo);
GetLastInputInfo(LInput);
d := (GetTickCount - LInput.dwTime) div 1000// Ausgabe in Sekunden

if d>5 then
   idleTimeLabel.Color:=clred // user inactive
else
   idleTimeLabel.Color:=clgreen; // user active

idleTimeLabel.Caption := Format('Idle since %d s', [d]);
end;


netspy - Fr 22.12.06 12:31

user profile iconalias5000 hat folgendes geschrieben:
Könntest du evtl. deinen Code nochmal posten, wenn du sagst, dass es bei dir geht?

Mein Code ist vom Prinzip nicht anders als deiner. Ich kann zumindest keine Unterschiede erkennen, die erklären würden, warum es bei dir nicht geht. Gerade habe ich aber noch eine bessere Möglichkeit gefunden, wie man Mouse- und Tastaturereignisse außerhalb der Anwendung auch ohne DLL abfangen kann.

http://www.delphipraxis.net/post654235.html

Das erste Beispiel von Mazel mit einem JournalRecordProc Hook funktioniert super, kommt ohne DLL aus und geht laut API-Doc ab Windows 95. Da das Ereignis dann im Thread-Kontext der eigenen Anwendung erfolgt, kann man sich SendMessage oder MMF auch gleich sparen.


alias5000 - Fr 22.12.06 15:23

Hm zwei sehr interessante Lösungen :) Vielen Dank!
Mal schaun, was ich dann verwende ;)

Gruß alias5000

Edit: Schade, Raffos Variante funktioniert erst ab Windows 2000, hat also noch größere Einschränkungen als mein Hook (ab Win NT). Ich versuch jetzt mal die andere Variante, die ja ab Win95 laufen soll :D


thepaine91 - Mo 26.10.09 12:35

Auch wenn das hier ganz schön alt ist Antworte ich trotzdem mal falls jemand auf das Thema stößt und das selbe Problem hat.

Dein Fehler ist sicher folgender:


Delphi-Quelltext
1:
var MHHandle: Cardinal;                    

MHHandle wird i-wo ein Wert zugewiesen. z.B.


Delphi-Quelltext
1:
2:
procedure installHook(Hwnd: Thandle);  // o. (Hwnd: Cardinal)
MHHandle := Hwnd;


Problem wäre in dem fall folgendes:
Daten in Dlls liegen immer im Aufrufendem Prozess d.h. nur dein eigenes Programm hat den Wert MHHandle dem zu folge funktioniert der Hook nur dann wenn die Maus auf deinem Fenster ist.
Ist sie auf einem anderen Bereich hat MHHandle i-einen Wert nur sicher nicht den richtigen.

In dem fall z.B. CreateFileMapping ansehen. Gibt noch mehr möglichkeiten dazu.