Recherche
1 connecté

  Actuellement : 0 commentaire Utiliser les HOOKS

Utiliser les HOOKS

Dernière mise à jour : 21/01/03

Télécharger l'exemple du tutorial.

Si vous désirez faire des HOOKS sur le CLAVIER, après avoir lu ce tutorial, allez voir l'exemple keyhook.zip dans le site.

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);
  • WH_MOUSE indique que l'on souhaite intercepter tous les messages de type "souris".

  • HookActionCallBack est le nom de notre procédure qui devra être appelée lorsque circulera un message de type "souris"

  • HookHandle récupère la valeur retournée par SetWindowsHookEx, c'est à dire le Handle de notre Hook. Ce Handle nous servira par la suite pour libérer le Hook à l'aide de la fonction UnHookWindowsHookEx.

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 :

  • La fonction InitialisationHook devra être appelée par notre programme, par exemple dans le OnCreate de la Form principale de notre application.

  • La fonction FinalisationHook pourra être appelée à la fermeture de notre programme. (Ex: dans l'événement OnDestroy de la fiche principale)

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 InitialisationHook(AWndCallBack:HWnd):HWnd; stdcall; export;
{SetWindowsHookEx permet de donner à Windows le nom de la fonction }
{ (ici HookActionCallBack) 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 FinalisationHook; 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 ce Handle (HookHandle dans notre exemple). 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 :

  • Initialiser la variable DllProc en lui passant l'adresse de la procédure contenant notre code d'initialisation. Pour cela, il suffit de placer par exemple, DllProc:=@LibraryProc; entre un begin et le end. final de notre dll

  • Implémenter la procédure dont l'adresse est contenue dans DllProc (ici LibraryProc) en testant si le paramètre transmis est égal à DLL_PROCESS_ATTACH.

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 WM_COPYDATA à notre application déclenchant ainsi une procédure dans notre application.
Ce type de message est fait pour transmettre des données d'une application à une autre.