Utiliser les HOOKS

Dernière mise à jour : 15/12/99

Télécharger l'exemple.

A quoi servent les HOOKS ?

Les Hooks permettent de récupérer les messages à destination des autres applications.

 

Introduction

Contrairement à la plupart des autres tutorials de ce site, on ne construira pas ensemble un exemple mais j'expliquerai tous les principes qu'il faut connaître afin de comprendre un exemple complet à télécharger.
Cet exemple a été fait d'après un exemple que m'avait fourni Daniel Drouin. Merci à lui. Sans lui, je n'aurai pas réussi à comprendre cette technique très peu documentée.

Principe général

Au départ, on crée un Hook en indiquant à Windows la fonction qui doit être déclenchée à chaque fois que transite un message d'un certain type.

HookHandle:=SetWindowsHookEx(WH_MOUSE,HookActionCallBack,HInstance,0);

Exemple de code (attention, ce code ne peut fonctionner tel quel, voir plus bas, il n'est là que pour la compréhension) :

Dans ce morceau de code exemple :

function HookActionCallBack(Code: integer; Msg: WPARAM; MouseHook: LPARAM):LRESULT; stdcall;
{cette fonction reçoit tous les messages détournés}                                            }
{elle bip si le message est de type WM_LBUTTONDOWN}
begin
  if Msg=WM_LBUTTONDOWN then messageBeep(1);
  Result:=CallNextHookEx(HookHandle,Code,Msg,MouseHook);//afin que le message continue à se propager
end;

function InitializeHook(AWndCallBack:HWnd):HWnd; stdcall; export;
{SetWindowsHookEx permet de donner à Windows le nom de la fonction qui sera }
{exécutée à chaque fois qu'il reçoit un message de type WH_MOUSE            }
begin
    HookHandle:=SetWindowsHookEx(WH_MOUSE,HookActionCallBack,HInstance,0);
end;
procedure FinalizeHook; stdcall; export;
begin
    UnhookWindowsHookEx(HookHandle);
end;

 

L'appel à SetWindowsHookEx et la fonction appelée doivent être dans une DLL

Le but de la fonction SetWindowsHook est donc de donner à Windows le nom d'une fonction qu'il doit déclencher lui-même à chaque fois que certains messages transitent.

Il ne peut le faire que si cette fonction est placée dans une DLL. Exactement comme un programme normal qui ne peut se servir d'une fonction développée par un tiers que si elle se trouve dans une DLL (et autres ActiveX...) et non dans un programme normal.

Donc, il faut d'abord savoir créer une DLL puis y placer les fonctions du style de celles figurant dans l'exemple ci-dessus.

 

Certaines variables de la DLL doivent être dans un "espace partagé" !

Notre application fait appel à la DLL et certaines valeurs doivent être conservées en mémoire pour être réutilisée par la suite. Par exemple, SetWindowsHookEx renvoie le Handle du Hook, qu'il faut garder en mémoire afin de pouvoir utiliser CallNextHookEx dans la fonction appelée (HookActionCallBack dans notre exemple). Or, notre application va appeler à sa création SetWindowsHookEx et donc obtenir HookHandle. Mais, lorsque Windows fera appel à la fonction HookCallBack de notre DLL, il le fera dans une autre instance. C'est le même phénomène que lorsque vous lancez 2 fois la même application en simultané, si on ne fait rien de spécial, la première instance ne partage pas ses variables avec la deuxième.

Pour faire ce partage de variable, nous allons employer un "File-Mapping" :

   {le $FFFFFFFF indique seulement que ce n'est pas un fichier qui sera mappé, mais des données}
   {TDonneesHook.InstanceSize permet de donner à Windows la bonne taille de mémoire à réserver }
   HookMap:=CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,TDonneesHook.InstanceSize,'Michel');
   {Ensuite faire un View indiquant la structure dans laquelle seront stockées nos données.}
   DonneesHook:=MapViewOfFile(HookMap,FILE_MAP_WRITE,0,0,0);

 

Pour libérer la mémoire, il suffit, à la fin de l'utilisation de notre DLL de faire :

   UnMapViewOfFile(DonneesHook);
   CloseHandle(HookMap);

 

DonneesHook contiendra toutes les variables que l'on souhaite partager.

Exemple de déclaration :

  TDonneesHook=class
     HookHandle:HHook; {Handle retourné par SetWindowsHookEx}
     ........//autres variables à partager
  end;
.......
Var DonneesHook : TDonneesHook;
......
Pour utiliser ces données partagées, il faut, par exemple, remplacer 
HookHandle:=SetWindowsHookEx(WH_MOUSE,HookActionCallBack,HInstance,0);
par
DonneesHook.HookHandle:=SetWindowsHookEx(WH_MOUSE,HookActionCallBack,HInstance,0);
et 
Result:=CallNextHookEx(HookHandle,Code,Msg,MouseHook);

par

Result:=CallNextHookEx(DonneesHook.HookHandle,Code,Msg,MouseHook);

 

Où mettre le CreateFileMapping et le MapViewOfFile ?

Lorsqu'une DLL est chargée, une procédure désignée par la variable DllProc est exécuté en lui passant le paramètre DLL_PROCESS_ATTACH. Cela permet de lancer du code d'initialisation.
Il faut :

Cette même procédure désignée par DllProc permet de désigner du code à exécuter à la fin de l'utilisation de la DLL. En ce cas, le paramètre transmis à cette procédure est égal à DLL_PROCESS_DETACH. On l'utilisera pour libérer les ressources prises par notre FileMapping. Dans notre exemple :

UnMapViewOfFile(DonneesHook);
CloseHandle(HookMap);

D'où le code complet de notre LibraryProc :

procedure LibraryProc(AReason:Integer);
begin
  case AReason of
    DLL_PROCESS_ATTACH:begin
      {Il faut d'abord créer le FileMapping}
      {le $FFFFFFFF indique seulement que ce n'est pas un fichier qui sera mappé, mais des données}
      {TDonneesHook.InstanceSize permet de donner à Windows la bonne taille de mémoire à réserver}
      HookMap:=CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,TDonneesHook.InstanceSize,'Michel');
      {Ensuite faire un View sur tout le fichier}
      DonneesHook:=MapViewOfFile(HookMap,FILE_MAP_WRITE,0,0,0);
    end;
    DLL_PROCESS_DETACH:begin //libérer les ressources prisent par notre FileMapping
      UnMapViewOfFile(DonneesHook);
      CloseHandle(HookMap);
    end;
    DLL_THREAD_ATTACH:;
    DLL_THREAD_DETACH:; 
  end;
end;

exports
  InitializeHook,
  FinalizeHook;

begin
  DllProc:=@LibraryProc;
  LibraryProc(DLL_PROCESS_ATTACH);
end.

Et si on veut récupérer les messages dans notre application et non dans la DLL

Et bien ce n'est pas compliqué, il suffit que notre DLL envoie un message à notre application déclenchant ainsi une procédure dans notre application.

retour au sommaire