Cosa devo fare per fare in modo che la mia procedura hook WH_SHELL o WH_CBT riceva eventi da altri processi?

StackOverflow https://stackoverflow.com/questions/299370

  •  08-07-2019
  •  | 
  •  

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.
È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top