Cosa devo fare per fare in modo che la mia procedura hook WH_SHELL o WH_CBT riceva eventi da altri processi?
Domanda
Sto cercando di usare SetWindowsHookEx
per impostare un hook WH_SHELL
per ricevere notifiche su HSHELL_WINDOWCREATED a livello di sistema
e HSHELL_WINDOWDESTROYED
eventi. Passo 0 per l'argomento finale dwThreadId
che, secondo the docs , dovrebbe " associare la procedura hook a tutti i thread esistenti in esecuzione sullo stesso desktop del thread chiamante " ;. Passo anche l'handle alla mia DLL ( HInstance
in Delphi) per il parametro hMod
così come tutti gli esempi che ho visto.
Tuttavia, ricevo sempre e solo la notifica delle finestre create dalla mia app e, il più delle volte, i miei test fanno sì che il processo desktop vada in fiamme una volta chiusa l'app. Prima di chiedere, chiamo UnhookWindowsHookEx
. Chiamo sempre anche CallNextHookEx
dal mio gestore.
Sto eseguendo la mia app di prova da un account utente limitato ma finora non ho trovato alcun suggerimento che indichi che questo avrebbe un ruolo ... (anche se in realtà mi sorprende)
AFAICT, ho fatto tutto per il libro (ovviamente non l'ho fatto ma finora non riesco a vedere dove).
Sto usando Delphi (2007) ma non dovrebbe importare, penso.
MODIFICA: Forse avrei dovuto menzionarlo prima: ho scaricato e provato un paio di esempi (anche se sfortunatamente non ce ne sono molti disponibili per Delphi - specialmente nessuno per WH_SHELL o
WH_CBT
). Sebbene non blocchino il sistema come fa la mia app di test, non acquisiscono comunque eventi da altri processi (anche se posso verificare con ProcessExplorer che vengono caricati al loro posto). Quindi sembra che ci sia qualcosa di sbagliato nella mia configurazione di sistema o gli esempi siano sbagliati o semplicemente non è possibile catturare eventi da altri processi. Qualcuno può illuminarmi?
EDIT2: OK, ecco la fonte del mio progetto di test.
La DLL che contiene la procedura hook:
library HookHelper;
uses
Windows;
{$R *.res}
type
THookCallback = procedure(ACode, AWParam, ALParam: Integer); stdcall;
var
WndHookCallback: THookCallback;
Hook: HHook;
function HookProc(ACode, AWParam, ALParam: Integer): Integer; stdcall;
begin
Result := CallNextHookEx(Hook, ACode, AWParam, ALParam);
if ACode < 0 then Exit;
try
if Assigned(WndHookCallback)
// and (ACode in [HSHELL_WINDOWCREATED, HSHELL_WINDOWDESTROYED]) then
and (ACode in [HCBT_CREATEWND, HCBT_DESTROYWND]) then
WndHookCallback(ACode, AWParam, ALParam);
except
// plop!
end;
end;
procedure InitHook(ACallback: THookCallback); register;
begin
// Hook := SetWindowsHookEx(WH_SHELL, @HookProc, HInstance, 0);
Hook := SetWindowsHookEx(WH_CBT, @HookProc, HInstance, 0);
if Hook = 0 then
begin
// ShowMessage(SysErrorMessage(GetLastError));
end
else
begin
WndHookCallback := ACallback;
end;
end;
procedure UninitHook; register;
begin
if Hook <> 0 then
UnhookWindowsHookEx(Hook);
WndHookCallback := nil;
end;
exports
InitHook,
UninitHook;
begin
end.
E la forma principale dell'app usando l'hook:
unit MainFo;
interface
uses
Windows, SysUtils, Forms, Dialogs, Classes, Controls, Buttons, StdCtrls;
type
THookTest_Fo = class(TForm)
Hook_Btn: TSpeedButton;
Output_Lbx: TListBox;
Test_Btn: TButton;
procedure Hook_BtnClick(Sender: TObject);
procedure Test_BtnClick(Sender: TObject);
public
destructor Destroy; override;
end;
var
HookTest_Fo: THookTest_Fo;
implementation
{$R *.dfm}
type
THookCallback = procedure(ACode, AWParam, ALParam: Integer); stdcall;
procedure InitHook(const ACallback: THookCallback); register; external 'HookHelper.dll';
procedure UninitHook; register; external 'HookHelper.dll';
procedure HookCallback(ACode, AWParam, ALParam: Integer); stdcall;
begin
if Assigned(HookTest_Fo) then
case ACode of
// HSHELL_WINDOWCREATED:
HCBT_CREATEWND:
HookTest_Fo.Output_Lbx.Items.Add('created handle #' + IntToStr(AWParam));
// HSHELL_WINDOWDESTROYED:
HCBT_DESTROYWND:
HookTest_Fo.Output_Lbx.Items.Add('destroyed handle #' + IntToStr(AWParam));
else
HookTest_Fo.Output_Lbx.Items.Add(Format('code: %d, WParam: $%x, LParam: $%x', [ACode, AWParam, ALParam]));
end;
end;
procedure THookTest_Fo.Test_BtnClick(Sender: TObject);
begin
ShowMessage('Boo!');
end;
destructor THookTest_Fo.Destroy;
begin
UninitHook; // just to make sure
inherited;
end;
procedure THookTest_Fo.Hook_BtnClick(Sender: TObject);
begin
if Hook_Btn.Down then
InitHook(HookCallback)
else
UninitHook;
end;
end.
Soluzione
Il problema è che la DLL hook viene effettivamente caricata in diversi spazi indirizzo. Ogni volta che Windows rileva un evento in un processo esterno che deve essere elaborato dal hook, carica la DLL hook in quel processo (se non è già caricato, ovviamente).
Tuttavia, ogni processo ha il suo spazio indirizzo. Ciò significa che il puntatore alla funzione di callback che hai passato in InitHook () ha senso solo nel contesto del tuo EXE (ecco perché funziona per eventi nella tua app). In qualsiasi altro processo quel puntatore è immondizia ; può indicare una posizione di memoria non valida o (peggio) in una sezione di codice casuale. Il risultato può essere una violazione dell'accesso o un danneggiamento della memoria silenziosa.
Generalmente, la soluzione è usare una sorta di interprocess comunicazione (IPC) per notificare correttamente il tuo EXE. Il modo più indolore per il tuo caso sarebbe quello di pubblicare un messaggio e stipare le informazioni necessarie (evento e HWND) nella sua WPARAM / LPARAM. È possibile utilizzare un WM_APP + n o crearne uno con RegisterWindowMessage (). Assicurati che il messaggio sia pubblicato e non inviato, per evitare deadlock.
Altri suggerimenti
Questo potrebbe essere terziario per la tua domanda, ma come vedi, i ganci sono molto difficili da ottenere bene - se puoi evitare di usarlo in qualsiasi modo, fallo. Incontrerai tutti i tipi di problemi con loro, specialmente su Vista, dove dovrai affrontare UIPI.
Giusto per chiarire qualcosa che " efotinis " menzionato in merito alla pubblicazione di messaggi di nuovo nel tuo processo: wParam e lParam che pubblichi nel tuo processo principale non possono essere puntatori, possono semplicemente essere "numeri".
Ad esempio, supponiamo che si agganci il messaggio WM_WINDOWPOSCHANGING, Windows ti passa un puntatore a un WINDOWPOS nel lparam. Non puoi semplicemente riportare quel lparam al tuo processo principale perché la memoria a cui punta il lparam è valida solo nel processo che riceve il messaggio.
Questo è ciò che " efotinis " intendeva quando diceva " stipare le informazioni necessarie (evento e HWND) nella sua WPARAM / LPARAM " ;. Se desideri restituire messaggi più complessi, dovrai utilizzare altri IPC (come named pipe, TCP o file mappati in memoria).
Lol, sembra che l'errore sia nel codice di test.
Se crei due pulsanti separati, uno per Init e uno per UnInit (preferisco Exit).
procedure THooktest_FO.UnInitClick(Sender: TObject);
begin
UninitHook;
end;
procedure THooktest_FO.InitClick(Sender: TObject);
begin
InitHook(HookCallback)
end;
Avvia l'app. Fare clic su Init e quindi sul pulsante Test, viene visualizzato il seguente output:
created handle #1902442
destroyed handle #1902442
created handle #1967978
created handle #7276488
Quindi viene visualizzata la finestra di messaggio.
Se fai clic su OK ottieni:
destroyed handle #1967978
HTH
Ho trovato la documentazione di base di Delphi per SetWindowsHookEx. Ma il testo è un po 'vago.
function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc;
hmod: HInst; dwThreadId: DWORD): HHOOK;
-
hmod: un handle per il modulo (una DLL) contenente la funzione hook indicata dal parametro lpfn. Questo parametro deve essere impostato su zero se dwThreadId identifica un thread creato dal processo corrente, dlpfn punta a una funzione hook situata nel codice associato al processo corrente.
-
dwThreadId: l'identificatore del thread a cui verrà associata la funzione hook installata. Se questo parametro è impostato su zero, l'hook sarà un hook a livello di sistema associato a tutti i thread esistenti.
A proposito, per il parametro hmod avresti dovuto usare un handle di modulo. (HINSTANCE indica l'handle dell'applicazione).
hand := GetModuleHandle('hookhelper.dll');
Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);
Ma sebbene la mano differisca da HINSTANCE mostra ancora lo stesso risultato.