Dernière mise à jour : 15/12/99
Les Hooks permettent de récupérer les messages à destination des autres applications.
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.
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 InitializeHook devra être appelée par notre programme, par exemple dans le OnCreate de la Form principale de notre application.
La fonction FinalizeHook 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 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;
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.
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);
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 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.