Contrôle du clavier et de la souris sous Windows

gRRosminet

Traducteur

FW-S

- Titre : Contrôle du clavier et de la souris sous Windows

- Auteur : gRRosminet

- Traducteur : FW-S

- Parution : 10 novembre 2002

- Mise à jour : 29 décembre 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2012 gRRosminet. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Rappels sur la file de messages Windows

- III. Démarche de la simulation par la file de messages Windows

- III-A. Récupérer le handle de la fenêtre au premier plan

- III-B. Récupérer le handle d'une fenêtre précise

- III-C. Les différents messages pour le clavier et la souris

- III-C-1. Le clavier

- III-C-2. La souris

- III-D. Comment envoyer un message

- III-D-1. Le clavier

- III-D-2. La souris

- IV. Prendre le contrôle du clavier et de la souris

- IV-A. Le clavier dans tous ses états

- IV-B. Les poules picorent, la souris cliquette

- IV-B-1. Déplacer le curseur

- IV-B-2. Simuler les clics et la mollette

- V. Divers problèmes et leur voie de résolution

- V-A. Ciblage d'une fenêtre spécifique avec la fonction de contrôle du clavier

- V-B. Trouver le class name d'une fenêtre quelconque

- VI. Les hooks

- VI-A. Qu'est-ce qu'un hook ?

- VI-B. Les différents types de hooks

- VI-C. Prérequis à la mise en place d'un hook

- VI-D. Mise en place d'un hook

I - Introduction

Depuis ces dernières années, les applications multimédias fleurissent à foison : lecteurs MP3, DVD, DivX et autres logiciels de TV sont désormais monnaie courante sur les ordinateurs personnels qui autrefois étaient destinés à installer Office et le dernier jeu à la mode. Si les interfaces sont de plus en plus conviviales, elles nécessitent toujours que l'utilisateur vienne interagir soit avec le clavier, soit avec la souris pour réaliser les opérations usuelles telles que lecture, pause, rembobinage, changement de chaînes, etc. Nous avons donc vu apparaître des claviers multimédias aux merveilleuses touches complémentaires pour s'occuper de ces fonctions (comme si 105 touches ce n’était pas assez ! ;-) ). Cependant, si on regarde bien, la prise en charge de ces touches par les applications n'est pas toujours implémentée et quand elle l'est, elle ne l'est pas toujours correctement. En effet, il arrive que certaines applications réagissent ou non suivant le média lu ou suivant le temps écoulé depuis la dernière « vraie » action du clavier ou de la souris. Les développeurs ont donc eu l'idée de simuler les raccourcis clavier des applications multimédias pour mieux se faire comprendre des applications. Cependant, il n'est pas toujours évident de réaliser cette simulation et c'est pourquoi nous allons diviser cette étude en deux parties : simulation par la file de messages de Windows et prendre le contrôle du clavier et de la souris

II - Rappels sur la file de messages Windows

Le principal moyen de communication utilisé par Windows et les applications écrites pour ce système d'exploitation est la file de messages associée à chaque fenêtre. En effet, chaque fois qu'un événement se produit, Windows le traite en envoyant un message, soit à lui-même, soit à un autre programme. Le programme procède alors au traitement de ce message si celui-ci a un intérêt.

Nous allons étudier l'exemple suivant : vous êtes dans votre navigateur favori et vous tapez l'adresse : « https://www.developpez.net/forums/ » puis vous validez avec la touche ENTRÉE

À chaque pression d'une touche, Windows va envoyer trois messages au navigateur : WM_KEYDOWN qui va indiquer qu'une touche a été enfoncée, WM_CHAR qui va préciser qu'un caractère a été donné à l'application et finalement WM_KEYUP qui va indiquer que la touche a été relâchée. Chacun de ces messages est placé dans la file de messages du navigateur qui va les analyser un à un. Imaginons qu'il ne se préoccupe que du message WM_CHAR et qu'il ignore les autres. À chacun de ces messages, il va analyser les informations fournies par Windows en complément du message et ajouter le caractère indiqué à la chaîne de caractères représentant l'URL de la ressource désirée. Au moment où il va recevoir le caractère « ENTRÉE », il va lancer la recherche.

L'intérêt d'une telle étude est de comprendre par quel moyen on va pouvoir dire a une application multimédia (ou une autre, mais l’intérêt serait plus limité)  :« l'utilisateur a pressé la touche X : lance la lecture ». En effet, vous l'avez sûrement remarqué, la majorité des applications met à notre disposition des raccourcis clavier pour accéder rapidement aux principales fonctionnalités ; nous allons les simuler afin de contrôler les applications.

III - Démarche de la simulation par la file de messages Windows

Simuler l'interaction de l'utilisateur par la file de messages nécessite deux étapes que nous allons détailler ci-après : déterminer la cible et envoyer le message.

Tout d'abord, il faut déterminer le handle de l'application à laquelle on veut envoyer un message. Un handle est une sorte d'identifiant utilisé par Windows pour identifier chaque composant manipulé par l'interface. Deux possibilités s'offrent à nous : envoyer le message à une fenêtre précise ou envoyer le message à la fenêtre au premier plan.

III-A - Récupérer le handle de la fenêtre au premier plan

C'est la chose la plus simple. Cela se fait par un appel à la fonction GetForegroundWindow(). Cette fonction ne prend pas de paramètre et renvoie une valeur de type HWND (handle de fenêtre). Cette valeur vaut NULL en C ou nil en Pascal si la fonction a échoué. Voici un exemple d'utilisation en langage C :

#include <windows.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
HWND hFore = GetForegroundWindow();
printf("le handle de la fenêtre au premier plan est : 0x%X",hFore);
return 0;
}

III-B - Récupérer le handle d'une fenêtre précise

Cela nécessite de connaître le nom de la « classe » de composant à laquelle la fenêtre appartient. Le nom de classe ou class name correspond à un identifiant qu'on associe à une fenêtre pour la distinguer d'une autre. Par exemple, WinAMP dans ses versions 1.x et 2.x avait pour nom de classe « Winamp v1.x » ; le nom de classe du lecteur Windows Media est « Media Player 2 ».

Pour récupérer le handle d'une fenêtre dont on connaît le class name, on utilise la fonction FindWindow(). Cette fonction prend deux paramètres : un pointeur vers une chaîne de caractères contenant le class name de la fenêtre recherchée (char * en C, character^ en Pascal) et un pointeur vers une chaîne de caractères contenant le titre de la fenêtre recherchée (optionnel, mettre NULL en C ou nil en Pascal). Elle renvoie une valeur de type HWND (handle de fenêtre). Cette valeur vaut NULL en C ou nil en Pascal si la fonction a échoué. Voici un exemple d'utilisation en langage C :

#include <windows.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
HWND hMediaplayer = FindWindow("Media Player 2",NULL);
printf("le handle de la fenêtre de media player 2 est : 0x%X",hMediaplayer);
return 0;
}

Nous allons maintenant voir quel message envoyer et comment l'envoyer.

III-C - Les différents messages pour le clavier et la souris

Il existe trois messages pour répertorier les actions du clavier, trois messages pour chacun des trois boutons de la souris et un message pour la molette.

III-C-1 - Le clavier

WM_KEYDOWN : c'est le premier message qu'envoie Windows à une application lorsqu'on enfonce une touche du clavier.
WM_CHAR : il est envoyé au moment où WM_KEYDOWN est récupéré par l'application.
WM_KEYUP : c'est le dernier message qu'envoie Windows. Il est envoyé lorsque la touche est relâchée.

Nous avons donc trois messages à notre disposition pour simuler des actions au clavier. En général, l'utilisation de WM_KEYDOWN est suffisante, mais il arrive avec certaines applications (WinAMP 3, Lecteur Windows Media…) que ce ne soit pas suffisant. En effet, certaines applications prennent en compte l'envoi du premier message puis ignorent les suivants ; pour éviter cela, on peut, quand cela est nécessaire, simuler également le relâchement de la touche avec WM_KEYUP. Il est déconseillé de le faire dans tous les cas, car il arrive régulièrement que cela ait pour effet de dédoubler l'action et le résultat ne sera donc pas celui attendu.

III-C-2 - La souris

WM_LBUTTONDOWN : ce message correspond à l'enfoncement du bouton gauche de la souris.
WM_LBUTTONUP : ce message intervient lors du relâchement du bouton gauche de la souris.
WM_LBUTTONDBLCLK : ce message simule un double-clic du bouton gauche. Il est précédé des messages WM_LBUTTONDOWN, WM_LBUTTONUP et suivi de WM_LBUTTONUP.
WM_MOUSEWHEEL : ce message simule une rotation de la molette et indique l'amplitude de la rotation.

Chacun des trois premiers messages a son équivalent pour les boutons du milieu (mollette) et de droite en remplaçant respectivement L par M ou R. L'intérêt de ces trois messages est assez limité, car pour simuler le clic sur un bouton, il faut connaître son identificateur (unique et propre à l'application à laquelle il appartient) et c'est un message du type WM_COMMAND qu'il faut générer. Par contre, WM_MOUSEWHELL peut s'avérer utile, car il est fréquent que la molette de la souris permette de contrôler le volume ou de faire défiler des images.

III-D - Comment envoyer un message

L'objectif de cette partie est plus d'expliciter les paramètres qu'on va passer à la fonction PostMessage() que d'expliquer son fonctionnement. Si j'ai choisi d'utiliser PostMessage() plutôt que SendMessage(), c'est parce que j'ai considéré qu'on n'attendait pas de résultat de la part de l'application qu'on cherche à commander.

III-D-1 - Le clavier

Les messages utilisés pour manipuler le clavier utilisent tous les mêmes paramètres : le code de la touche d'une part et des informations de répétition et/ou d'état des touches système qui ne sont généralement pas utilisées par les applications d'autre part. Nous n'allons détailler que le premier puisque le second ne nous avancera pas dans la résolution de notre problème et que toutes les informations sont disponibles dans le SDK Microsoft (Win32 SDK).

Le premier paramètre est donc le code « virtuel » de la touche pressée. Les codes sont des valeurs prédéfinies dans les entêtes Windows et commencent par VK. Voici un tableau récapitulatif :

Symbolic constant name

Value (hexadecimal)

Mouse or keyboard equivalent

VK_LBUTTON

01

Left mouse button

VK_RBUTTON

02

Right mouse button

VK_CANCEL

03

Control-break processing

VK_MBUTTON

04

Middle mouse button (three-button mouse)

-

05-07

Undefined

VK_BACK

08

BACKSPACE key

VK_TAB

09

TAB key

-

0A-0B

Undefined

VK_CLEAR

0C

CLEAR key

VK_RETURN

0D

ENTER key

-

0E-0F

Undefined

VK_SHIFT

10

SHIFT key

VK_CONTROL

11

CTRL key

VK_MENU

12

ALT key

VK_PAUSE

13

PAUSE key

VK_CAPITAL

14

CAPS LOCK key

-

15-19

Reserved for Kanji systems

-

1A

Undefined

VK_ESCAPE

1B

ESC key

-

1C-1F

Reserved for Kanji systems

VK_SPACE

20

SPACEBAR

VK_PRIOR

21

PAGE UP key

VK_NEXT

22

PAGE DOWN key

VK_END

23

END key

VK_HOME

24

HOME key

VK_LEFT

25

LEFT ARROW key

VK_UP

26

UP ARROW key

VK_RIGHT

27

RIGHT ARROW key

VK_DOWN

28

DOWN ARROW key

VK_SELECT

29

SELECT key

-

2A

Original equipment manufacturer (OEM) specific

VK_EXECUTE

2B

EXECUTE key

VK_SNAPSHOT

2C

PRINT SCREEN key for Windows 3.0 and later

VK_INSERT

2D

INS key

VK_DELETE

2E

DEL key

VK_HELP

2F

HELP key

VK_0

30

0 key

VK_1

31

1 key

VK_2

32

2 key

VK_3

33

3 key

VK_4

34

4 key

VK_5

35

5 key

VK_6

36

6 key

VK_7

37

7 key

VK_8

38

8 key

VK_9

39

9 key

-

3A-40

Undefined

VK_A

41

A key

VK_B

42

B key

VK_C

43

C key

VK_D

44

D key

VK_E

45

E key

VK_F

46

F key

VK_G

47

G key

VK_H

48

H key

VK_I

49

I key

VK_J

4A

J key

VK_K

4B

K key

VK_L

4C

L key

VK_M

4D

M key

VK_N

4E

N key

VK_O

4F

O key

VK_P

50

P key

VK_Q

51

Q key

VK_R

52

R key

VK_S

53

S key

VK_T

54

T key

VK_U

55

U key

VK_V

56

V key

VK_W

57

W key

VK_X

58

X key

VK_Y

59

Y key

VK_Z

5A

Z key

VK_LWIN

5B

Left Windows key (Microsoft Natural Keyboard)

VK_RWIN

5C

Right Windows key (Microsoft Natural Keyboard)

VK_APPS

5D

Applications key (Microsoft Natural Keyboard)

-

5E-5F

Undefined

VK_NUMPAD0

60

Numeric keypad 0 key

VK_NUMPAD1

61

Numeric keypad 1 key

VK_NUMPAD2

62

Numeric keypad 2 key

VK_NUMPAD3

63

Numeric keypad 3 key

VK_NUMPAD4

64

Numeric keypad 4 key

VK_NUMPAD5

65

Numeric keypad 5 key

VK_NUMPAD6

66

Numeric keypad 6 key

VK_NUMPAD7

67

Numeric keypad 7 key

VK_NUMPAD8

68

Numeric keypad 8 key

VK_NUMPAD9

69

Numeric keypad 9 key

VK_MULTIPLY

6A

Multiply key

VK_ADD

6B

Add key

VK_SEPARATOR

6C

Separator key

VK_SUBTRACT

6D

Subtract key

VK_DECIMAL

6E

Decimal key

VK_DIVIDE

6F

Divide key

VK_F1

70

F1 key

VK_F2

71

F2 key

VK_F3

72

F3 key

VK_F4

73

F4 key

VK_F5

74

F5 key

VK_F6

75

F6 key

VK_F7

76

F7 key

VK_F8

77

F8 key

VK_F9

78

F9 key

VK_F10

79

F10 key

VK_F11

7A

F11 key

VK_F12

7B

F12 key

VK_F13

7C

F13 key

VK_F14

7D

F14 key

VK_F15

7E

F15 key

VK_F16

7F

F16 key

VK_F17

80H

F17 key

VK_F18

81H

F18 key

VK_F19

82H

F19 key

VK_F20

83H

F20 key

VK_F21

84H

F21 key

VK_F22

85H

F22 key

VK_F23

86H

F23 key

VK_F24

87H

F24 key

-

88-8F

Unassigned

VK_NUMLOCK

90

NUM LOCK key

VK_SCROLL

91

SCROLL LOCK key

-

92-B9

Unassigned

-

BA-C0

OEM specific

-

C1-DA

Unassigned

-

DB-E4

OEM specific

-

E5

Unassigned

-

E6

OEM specific

-

E7-E8

Unassigned

-

E9-F5

OEM specific

VK_ATTN

F6

Attn key

VK_CRSEL

F7

CrSel key

VK_EXSEL

F8

ExSel key

VK_EREOF

F9

Erase EOF key

VK_PLAY

FA

Play key

VK_ZOOM

FB

Zoom key

VK_NONAME

FC

Reserved for future use.

VK_PA1

FD

PA1 key

VK_OEM_CLEAR

FE

Clear key

Ce tableau a été extrait de l'aide du SDK Win32 de Microsoft.

Il arrive que les éditeurs de compilateurs ne définissent pas les constantes VK_A à VK_Z et cela pour la simple et bonne raison que c'est en réalité la valeur du caractère majuscule correspondant. Dans ce cas, il suffit de remplacer VK_A par 'A' et ainsi de suite pour les autres lettres de l'alphabet.

Voyons un exemple d'utilisation de la fonction PostMessage(). Ce programme va rechercher une instance du lecteur Windows Media et lancer la lecture en simulant la pression de la barre d'espace puis le mettre en pause :

#include <windows.h>
int main(int argc, char * argv[])
{
HWND hMPlayer = FindWindow("Media Player 2",NULL);
SetForegroundWindow(hMPlayer);
Sleep(100);
PostMessage(hMPlayer,WM_KEYDOWN,VK_SPACE,0);
Sleep(5000);
PostMessage(hMPlayer,WM_KEYUP,VK_SPACE,0);
return 0;
}

Nous savons maintenant simuler des actions du clavier, mais il n'est cependant toujours pas possible de simuler la pression d'un raccourci clavier plus complexe qu'une touche tel que ALT + ENTRÉE.

III-D-2 - La souris

Comme je l'ai dit dans la partie précédente, l’intérêt des événements WM_LBUTTONDOWN, WM_LBUTTONUP et WM_LBUTTONDBLCLK est minime dans notre démarche, nous n'allons donc étudier que l'utilisation du message WM_MOUSEWHEEL.

Le message WM_MOUSEWHEEL fournit quatre informations de 16 bits chacune, réparties entre les mots hauts et bas des deux arguments (32 bits chacun) de PostMessage() restants à notre disposition. Voyons quelles sont les informations à fournir.
Tout d'abord, il y a les états des touches système et des trois boutons de la souris. Ceux-ci sont représentés à l'aide de masques prédéfinis dans le SDK Microsoft par les constantes : MK_CONTROL (Contrôle), MK_SHIFT (Majuscule), MK_LBUTTON, MK_MBUTTON et MK_RBUTTON (boutons gauche, milieu et droit de la souris). Il n'y a aucune garantie que l'application cible s'en serve.

Ensuite, il y a la valeur de la rotation de la molette. Une valeur positive représente une rotation vers le haut (vers le fil de la souris) et une valeur négative représente une rotation vers le bas (vers l'utilisateur). La rotation équivalant à un « cran » de la molette est définie par la constante WHEEL_DELTA (120). Il est possible d'utiliser des valeurs plus précises, mais toutes les applications n'en tiennent pas compte.

Finalement, il y a la positon verticale et horizontale du curseur au moment de l'événement. Là encore, il n'y a aucune garantie que l'application cible s'en serve.

Voyons maintenant comment construire les arguments de PostMessage() à partir de ces informations. La position du curseur doit être placée dans le second paramètre et les autres informations dans le premier. L'exemple ci-dessous montre comment le faire en C :

int iPosX,iPosY;
int iMasqueTouches;
int iDeplMolette;
DWORD dwWParam,dwLParam;
HWND hWindow;
// initialisation des variables avec les valeurs souhaitées
dwLParam = iPosY << 16 | iPosX; // on effectue un décalage de 16 bits vers la gauche
dwWParam = iDeplMolette << 16 | iMasqueTouches;
PostMessage(hWindow,WM_MOUSEWHEEL, dwWParam, dwLParam);

Une autre solution peut consister à utiliser une structure :

struct _dword {
short w1;
short w2;
} dwWParam, dwLParam;
int iPosX,iPosY;
int iMasqueTouches;
int iDeplMolette;
HWND hWindow;
// initialisation des variables avec les valeurs souhaitées
dwLParam.w1 = iPosY;
dwLParam.w2 = iPosX;
dwWParam.w1 = iDeplMolette;
dwWParam.w2 = iMasqueTouches;
PostMessage(hWindow,WM_MOUSEWHEEL, (DWORD)(dwWParam), (DWORD)(dwLParam));

Voilà, nous avons maintenant tous les éléments dont nous avions besoin pour simuler des actions simples à l'attention d'une application déterminée. Cependant, cela n'est pas suffisant, car on ne peut toujours pas faire de simulation globale ni déplacer la souris : il n'est donc pas possible d'interfacer un périphérique multimédia tel qu'un joystick avec la souris et le clavier. C'est pourquoi nous allons voir dans une deuxième partie comment simuler le clavier et la souris à un niveau beaucoup plus proche de la machine.

IV - Prendre le contrôle du clavier et de la souris

Comme nous l'avons vu dans la partie précédente, il n'est pas toujours possible d'obtenir le résultat attendu avec la file de messages de Windows. On va donc devoir intervenir à un niveau beaucoup plus proche du matériel. En effet, nous allons agir comme le ferait un pilote de périphérique pour la souris ou le clavier. Vous devez être en train de vous dire houlala… il va nous sortir des commandes en assembleur de derrière les fagots. Rassurez-vous, il n'en est rien : nous n'utiliserons que des fonctions de l'API Win32. Nous allons comme à l'habitude commencer par traiter le sujet du clavier, puis nous verrons que le fonctionnement de la souris y ressemble beaucoup.

IV-A - Le clavier dans tous ses états

Dans cette partie, nous allons voir comment faire croire au système qu'une touche est pressée ou non. Si je parle ici de système et non pas d'application, c'est parce que la fonction que nous allons utiliser n'a pas conscience d'une telle chose. Pour elle, il n'y a qu'un seul logiciel en cours : Windows ; et elle se contente de lui indiquer les modifications d'état du clavier. Il faudra donc prendre en compte lors des futurs développements qu'il n'est pas possible de dire « je veux simuler telle action pour telle fenêtre ».

Pour comprendre le fonctionnement du principe que nous allons voir, il faut considérer que le programme qu'on est en train de réaliser est en réalité le pilote d'un périphérique réel et agir en tant que tel. Les pilotes de périphérique des claviers utilisent la fonction keybd_event() pour indiquer au système l'état des différentes touches. Comme son nom l'indique, cette fonction ne signalera au système que les changements d'état des touches. Il n'y aura donc pas à intercepter les significations d'état que pourraient être amenés à faire d'autres pilotes de périphériques si le système avait voulu qu'on l'informe toutes les n millisecondes de l'état de chacune des touches. Voyons maintenant quels sont les paramètres que la fonction keybd_event() attend.

La fonction keybd() prend quatre paramètres : le code touche virtuel (ENTRÉE, A, F1…), le code matériel de la touche (touche n° 1, n° 2…), un masque indiquant si la touche est une touche étendue et si la touche est relâchée et finalement un double mot contenant des informations supplémentaires. Les codes de touches virtuels ont été présentés dans la partie précédente. Les codes matériels de touche ne sont pas utiles pour nous, car ils correspondent à une étape antérieure des pilotes de périphérique ; nous le laisserons donc à 0. Le masque est composé à partir de KEYEVENTF_EXTENDEDKEY qui indique que la touche est soit AltGr soit Ctrl droite, et KEYEVENTF_KEYUP qui indique que la touche est relâchée. Finalement le dernier paramètre est une donnée complémentaire qui ne nous sert à rien non plus, on le laissera donc à 0. Voici un exemple dans lequel on considère que par exemple le lecteur Windows Media est en train de lire une vidéo. Notre programme exemple va simuler la pression de Alt + Entrée pour faire passer le lecteur en mode plein écran.

#include <windows.h>
int main(int argc, char *argv[])
{
SetForegroundWindow(FindWindow("Media Player 2",NULL));
Sleep(100);
keybd_event(VK_LMENU,0,0,0);
keybd_event(VK_RETURN,0,0,0);
keybd_event(VK_RETURN,0,KEYEVENTF_KEYUP,0);
keybd_event(VK_LMENU,0,KEYEVENTF_KEYUP,0);
return 0;
}

IV-B - Les poules picorent, la souris cliquette

De la même façon que précédemment, nous allons faire croire au système qu'il y a eu un clic ou un mouvement de souris. Contrairement au cas du clavier, nous allons avoir besoin de plusieurs fonctions. Nous allons donc partager cette partie en deux thèmes : le déplacement du curseur d'une part et les clics et autres tours de molettes d'autre part.

IV-B-1 - Déplacer le curseur

Pour déplacer le curseur, nous allons utiliser la fonction SetCursorPos(). Cependant, si on veut déplacer le curseur, il va d'abord falloir déterminer sa position actuelle puis indiquer à Windows quelle est sa nouvelle position ; c'est ce que nous allons faire avec la fonction GetCursorPos().

La fonction GetCursorPos() prend en paramètre un pointeur sur une structure POINT qui contient deux entiers longs (type LONG de l'API) : la position x et y du curseur. La fonction SetCursorPos() prend en paramètre deux entiers les coordonnées x et y où placer le curseur. Le point d'origine est le coin supérieur gauche de l'écran. Voici un exemple qui va récupérer la position du curseur et le déplacer en fonction de la touche fléchée enfoncée :

#include <windows.h>
int main(int argc, char *argv[])
{
POINT pt;
BOOL bContinue = TRUE;
const SHORT Mask = 32768;
while (bContinue)
{
if (GetKeyState(VK_ESCAPE) & Mask)
bContinue = FALSE;
GetCursorPos(&pt);
if (GetKeyState(VK_UP) & Mask)
pt.y -= 1;
if (GetKeyState(VK_DOWN) & Mask)
pt.y += 1;
if (GetKeyState(VK_LEFT) & Mask)
pt.x -= 1;
if (GetKeyState(VK_RIGHT) & Mask)
pt.x += 1;
SetCursorPos(pt.x,pt.y);
Sleep(10);
}
return 0;
}

IV-B-2 - Simuler les clics et la mollette

Pour simuler les clics et la molette, nous allons utiliser une fonction très semblable à celle utilisée pour le clavier : mouse_event(). Cette fonction prend cinq paramètres. Le premier est un double mot qui contient des bits drapeaux pour indiquer l'événement qui a eu lieu. Le second et le troisième paramètre sont les coordonnées x et y du curseur au moment où l'événement a eu lieu. Le quatrième paramètre est la valeur de la rotation de la mollette. Le dernier paramètre est une information complémentaire sur 32 bits dont nous ne nous servirons pas.

Revenons un instant sur les drapeaux qui décrivent l'événement.

MOUSEEVENTF_ABSOLUTE

Indique que les coordonnées x et y données sont exprimées à partir du coin supérieur gauche de l'écran. Si ce drapeau n'est pas activé, les coordonnées sont relatives au dernier rapport de position. Il est fortement conseillé de donner des coordonnées absolues, car on n'a aucun moyen de contrôle pour affirmer quelle était la position précédente.

MOUSEEVENTF_MOVE

un mouvement vient d'avoir lieu

MOUSEEVENTF_LEFTDOWN

Le bouton gauche est baissé

MOUSEEVENTF_LEFTUP

Le bouton gauche est remonté

MOUSEEVENTF_RIGHTDOWN

Le bouton droit est baissé

MOUSEEVENTF_RIGHTUP

Le bouton droit est remonté

MOUSEEVENTF_MIDDLEDOWN

Le bouton du milieu est baissé

MOUSEEVENTF_MIDDLEUP

Le bouton du milieu est remonté

MOUSEEVENTF_WHEEL

La molette a effectué une rotation dont la quantité est indiquée dans l'avant-dernier paramètre

Voyons maintenant un exemple. Nous allons compléter l'exemple précédent en ajoutant les fonctionnalités suivantes : les touches 1, 2 et 3 du pavé numérique seront les trois boutons de la souris, le - du pavé numérique sera la mollette vers le haut et le + la mollette vers le bas.

#include <windows.h>
int main(int argc, char *argv[])
{
POINT pt;
BOOL bContinue = TRUE;
SHORT Mask = 32768;
DWORD dwEventFlags;
DWORD dwData;

while (bContinue)
{
dwData = 0;
dwEventFlags = MOUSEEVENTF_ABSOLUTE;
if (GetKeyState(VK_ESCAPE) & Mask)
bContinue = FALSE;
GetCursorPos(&pt);
if (GetKeyState(VK_NUMPAD1) & Mask)
{
if (!(GetKeyState(VK_LBUTTON) & Mask))
dwEventFlags |= MOUSEEVENTF_LEFTDOWN;
}
else if (GetKeyState(VK_LBUTTON) & Mask)
// Ce test est nécessaire pour s'assurer qu'on n'envoie pas plus
// de relevés de touche que d'abaissements
dwEventFlags |= MOUSEEVENTF_LEFTUP;
if (GetKeyState(VK_NUMPAD2) & Mask)
{
if (!(GetKeyState(VK_MBUTTON) & Mask))
dwEventFlags |= MOUSEEVENTF_MIDDLEDOWN;
}
else if (GetKeyState(VK_MBUTTON) & Mask)
dwEventFlags |= MOUSEEVENTF_MIDDLEUP;
if (GetKeyState(VK_NUMPAD3) & Mask)
{
if (!(GetKeyState(VK_RBUTTON) & Mask))
dwEventFlags |= MOUSEEVENTF_RIGHTDOWN;
}
else if GetKeyState(VK_R3BUTTON) & Mask)
dwEventFlags |= MOUSEEVENTF_RIGHTUP;
if (GetKeyState(VK_SUBTRACT) & Mask)
{
dwEventFlags |= MOUSEEVENTF_WHEEL;
dwData += WHEEL_DELTA;
}
else if (GetKeyState(VK_ADD) & Mask)
{
dwEventFlags |= MOUSEEVENTF_WHEEL;
dwData -= WHEEL_DELTA;
}
if (GetKeyState(VK_UP) & Mask)
pt.y -= 1;
if (GetKeyState(VK_DOWN) & Mask)
pt.y += 1;
if (GetKeyState(VK_LEFT) & Mask)
pt.x -= 1;
if (GetKeyState(VK_RIGHT) & Mask)
pt.x += 1;
SetCursorPos(pt.x,pt.y);
mouse_event(dwEventFlags,pt.x,pt.y,dwData,0);
Sleep(10);
}
return 0;
}

Maintenant que nous avons vu tous les moyens mis à notre disposition pour résoudre les problèmes que nous pose le contrôle d'applications tierces, il ne nous reste plus qu'à résoudre certains problèmes qui peuvent apparaître dans leur mise en œuvre.

V - Divers problèmes et leur voie de résolution

Dans certains cas, il serait nécessaire d'avoir un hybride des deux méthodes présentées dans cet exposé. En effet, il est souvent nécessaire de simuler un raccourci clavier complexe utilisant une touche modificateur telle que Alt ou Ctrl, ce qui nous oriente vers une solution de type « contrôle du clavier », mais on voudrait en plus pouvoir indiquer à quelle fenêtre envoyer le message. Il arrive également qu'on ne connaisse pas la fenêtre à laquelle envoyer le message. Je vous propose ici diverses solutions pour résoudre ce genre de problèmes.

V-A - Ciblage d'une fenêtre spécifique avec la fonction de contrôle du clavier

Comme vous avez pu le remarquer, on aurait bien souvent besoin d'une fonction hybride entre PostMessage() et keybd_event(). En effet, il n'est pas possible de simuler le raccourci clavier ALT + ENTRÉE avec la fonction PostMessage() et l'utilisation de keybd_event() ne permet pas de limiter l'événement à une application cible. Par ailleurs, une combinaison de ces deux fonctions ne résout pas le problème à coup sûr. Le problème vient du fait que la fonction PostMessage() n'attend pas que le message ait été traité par l'application cible avant de rendre la main à notre programme de contrôle. Pour parer à ce problème, on est obligé d'utiliser la fonction SendMessage() qui a le même prototype que PostMessage(), mais qui attend que le message ait été traité pour rendre la main. Cependant, si l'application cible ne rend pas la main très rapidement il y a de grandes chances pour que l'utilisateur appuie sur une touche du clavier pendant ce laps de temps et on ne peut pas mesurer les conséquences de cette interférence. La seule solution qui nous reste est donc d'espérer que l'utilisateur sera loin de son clavier au moment où on effectuera la simulation.

Voyons un bref exemple de simulation pseudociblée d'un raccourci clavier. Nous allons faire passer l'affichage de la vidéo du lecteur Windows Media en plein écran (ou inverse)

#include <windows.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
HWND hMediaplayer = FindWindow("Media Player 2",NULL);
if (hMediaplayer)
{
keybd_event(VK_LMENU,0,0,0);
SendMessage(hMediaPlayer,WM_CHAR,VK_RETURN,0);
keybd_event(VK_LMENU,0,KEYEVENTF_KEYUP,0);
}
return 0;
}

V-B - Trouver le class name d'une fenêtre quelconque

Voici un exemple de programme permettant de retrouver le class name d'une fenêtre quelconque. Celui-ci est facilement adaptable à une interface graphique pour que n'importe quel utilisateur puisse fournir le class name au programme qui en a besoin.

#include <windows.h>
#include <stdio.h>
#include <conio.h>
int main(int argc, char * argv[])
{
HWND hFore;
char szBuffer[128];
int iNbChar;

printf("Vous avez 5 secondes pour activer la fenêtre dont vous voulez connaître le class name\r\n");
printf("Appuyez sur une touche pour déclencher le compteur ...");
getch();
Sleep(5000);

hFore = GetForegroundWindow();
iNbChar = GetClassName(hFore,szBuffer,128);
if (iNbChar && (iNbChar < 128))
{
printf("\r\nLe class name de cette fenêtre est :\r\n|%s|",szBuffer);
printf("\r\nLes barres verticales (|) de gauche et droite n'en font pas partie\r\n");
}
else
{
printf("Impossible de récupérer le class name ou celui-ci est trop long\r\n");
}
return 0;
}

VI - Les hooks

(non non, ici on ne parle pas du capitaine Crochet ;-))

L'objectif d'une application qui contrôle le clavier ou la souris n'est pas toujours de simuler un comportement, mais peut également être de réagir à un comportement de l'utilisateur. Par ailleurs, il peut être utile d'étudier le comportement d'une application pour mieux la contrôler par la suite. Nous allons voir comment faire cela au travers des hooks. Nous verrons tout d'abord ce qu'est un hook, les différents types de hooks que propose Windows, les prérequis pour la mise en place d'un hook, puis comment les mettre en place.

VI-A - Qu'est-ce qu'un hook ?

Un hook est un moyen de contrôler ce qui transite par le système de gestion de messages de Windows : c'est comme si on dénudait un fil électrique et qu'on y branchait un appareil de mesure pour voir ce qui s'y passe. La mise en place d'un hook va permettre de demander au système d'exploitation de faire un appel à une fonction de notre cru à chaque fois qu'il rencontrera un certain type de message et avant de le transmettre à l'application cible.

VI-B - Les différents types de hooks

L'API Win32 propose actuellement 11 types de hooks dont deux sont spécialisés en deux sous-types chacun soit un total de 13 hooks possibles. Chaque type de hook permet à une application de surveiller différents aspects du mécanisme de gestion des messages de Windows.

Voici un bref descriptif de chacun d'entre eux :

Constante

Action hookée

Portée

WH_CALLWNDPROC

voir les messages envoyés par SendMessage avant l'application destinataire.

Thread ou Système

WH_CALLWNDPROCRET

idem mais après traitement.

Thread ou Système

WH_CBT

contrôler et autoriser les actions essentielles (activation, création, destruction, réduction, agrandissement, déplacement, redimensionnement d'une fenêtre, avant de terminer une commande système, avant de retirer un événement de la souris ou du clavier de la file de messages, avant d'appliquer le focus, avant de synchroniser l'application avec la file de messages)

Thread ou Système

WH_DEBUG

autoriser ou non l'appel des fonctions associées aux autres hooks, car il est appelé avant n'importe quel autre type de hook.

Thread ou Système

WH_FOREGROUNDIDLE

effectuer des applications de basse priorité quand le thread principal de l'application est inactif.

Thread ou Système

WH_GETMESSAGE

contrôler tout type de message envoyé à une application.

Thread ou Système

WH_JOURNALPLAYBACK

insérer des messages enregistrés avec WH_JOURNALRECORD dans la file système.

Système

WH_JOURNALRECORD

enregistrer une séquence d'événements pour les rejouer plus tard.

Système

WH_KEYBOARD

intercepter les messages de type WM_KEYDOWN et WM_KEYUP.

Thread ou Système

WH_MOUSE

intercepter les messages de la souris.

Thread ou Système

WH_MSGFILTER

intercepter les messages destinés aux menus, ascenseurs, boites de dialogue et de détecter si une fenêtre va être active par ALT+TAB ou ALT+ESC de l'application.

Thread ou Système

WH_SYSMSGFILTER

idem, mais pour toutes les applications

Système

WH_SHELL

recevoir les notifications importantes (une fenêtre au premier plan est sur le point d'être créée ou détruite)

Thread ou Système

Ce tutoriel étant destiné à la gestion du clavier et de la souris, nous ne verrons que les hooks WH_MOUSE et WH_KEYBOARD, mais les autres hooks s'utilisent exactement de la même façon.

VI-C - Prérequis à la mise en place d'un hook

Deux problèmes se posent lorsqu'on veut créer un hook. Premièrement, si celui-ci est destiné à observer les événements du système complet, on ne peut pas implémenter la fonction associée à ce crochet dans un programme séparé, mais on est obligé de le faire dans une DLL séparée. 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. Deuxièmement, la fonction qui gère le hook doit transmettre le message qu'elle a reçu dans la chaîne et pour cela elle a besoin du handle du hook ; hors, si on pourrait être tenté de stocker cette information de manière globale, cela ne résout en rien le problème, car il existe en réalité une instance de la DLL par processus recevant des messages ce qui implique que le handle ne serait connu que du processus appelant et que cela provoquerait des erreurs dans les autres applications. On va donc devoir créer une zone de mémoire nommée afin que toutes les instances de la DLL puissent accéder à ce handle de hook.

Dans l'exemple exposé dans la partie suivante, on considérera qu'on évolue sous Borland C++ Builder et qu'on a utilisé l'expert DLL. Pour des informations complémentaires concernant la création de DLL dans BC++ Builder, voyez ce tutoriel de LFE.

VI-D - Mise en place d'un hook

Pour mettre en place un hook, il suffit d'un appel à la fonction SetWindowsHookEx() et pour l'arrêter nous ferons appel à UnhookWindowsHookEx(). SetWindowsHookEx() prend quatre arguments : le type de hook à mettre en place, le pointeur sur la fonction à utiliser pour le hook, l'instance de la DLL contenant la fonction de hook et finalement l'identifiant du thread à hooker. Elle renvoie le handle du hook créé qui a pour la valeur NULL en cas d'échec. UnhookWindowsHookEx() prend comme seul paramètre le handle du hook à défaire et renvoie la constante FALSE si elle échoue.

Voyons le prototype de la fonction de hook :

LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);

Pour chaque type de hook, ces trois paramètres ont une signification particulière : nCode est un indicateur pour l'action à effectuer et wParam et lParam contiennent les informations du message qui a été envoyé. La valeur renvoyée par cette fonction doit être celle renvoyée par la fonction de propagation du message, CallNextHookEx(). Cette fonction prend en paramètres le handle du hook qu'on vient de gérer, suivi des arguments reçus par HookProc(), éventuellement modifiés. Si le message est intercepté par le hook (pas d'appel à CallNextHookEx()), la valeur renvoyée par HookProc() devrait être nulle (0L en C).

Détaillons maintenant le contenu des paramètres pour les fonctions associées à WH_KEYBOARD et à WH_MOUSE.

- WH_KEYBOARD
Le paramètre nCode prend sa valeur parmi HC_ACTION et HC_NOREMOVE. Dans le cas (rare) de la seconde valeur, cela veut dire qu'une application a lu le message sans le retirer de la file par un appel à PeekMessage() spécifiant le drapeau PM_NOREMOVE. Dans tous les cas, wParam et lParam contiennent les informations à propos de l'événement clavier. Il peut arriver que ce paramètre ait une valeur négative, la fonction doit alors immédiatement se terminer dans un appel à CallNextHookEx().
Les paramètres wParam et lParam contiennent les mêmes informations que dans le cas de WM_KEYDOWN ou WM_KEYUP : le code virtuel de la touche actionnée et les informations de répétition, d'état…

- WH_MOUSE
Le paramètre nCode prend sa valeur parmi HC_ACTION et HC_NOREMOVE. Dans le cas (rare) de la seconde valeur, cela veut dire qu'une application a lu le message sans le retirer de la file par un appel à PeekMessage() spécifiant le drapeau PM_NOREMOVE. Dans tous les cas, wParam et lParam contiennent les informations à propos de l'événement souris. Il peut arriver que ce paramètre ait une valeur négative, la fonction doit alors immédiatement se terminer dans un appel à CallNextHookEx().
Le paramètre wParam contient le type du message (WM_LBUTTONDOWN à WM_MOUSEWHEEL)
Le paramètre lParam contient un pointeur sur une structure MOUSEHOOKSTRUCT qui, elle, contient une structure POINT indiquant la position de la souris ; le handle de la fenêtre qui va recevoir le message ; le type de cible qui se trouve sous la souris et finalement des informations complémentaires pour le message. Voici un tableau exhaustif des types de cibles.

Valeur

Position du curseur

HTBORDER

Sur le bord d'une fenêtre qui ne peut pas être redimensionnée

HTBOTTOM

Sur le bord horizontal inférieur d'une fenêtre

HTBOTTOMLEFT

Sur le coin inférieur gauche d'une fenêtre

HTBOTTOMRIGHT

Sur le coin inférieur droit d'une fenêtre

HTCAPTION

Sur une barre de titre

HTCLIENT

Sur une zone cliente

HTERROR

Sur le fond d'écran ou sur une ligne de séparation entre deux fenêtres (pareil que HTNOWHERE, à l'exception que la fonction DefWindowProc() produit un beep système pour indiquer une erreur)

HTGROWBOX

Sur une boite de redimensionnement (pareil que HTSIZE)

HTHSCROLL

Sur un ascenseur horizontal

HTLEFT

Sur le bord gauche d'une fenêtre

HTMENU

Sur un menu

HTNOWHERE

Sur le fond d'écran ou sur une ligne de séparation entre deux fenêtres

HTREDUCE

Sur un bouton de minimisation

HTRIGHT

Sur le bord droit d'une fenêtre

HTSIZE

Sur une boite de redimensionnement (pareil que HTGROWBOX)

HTSYSMENU

Sur un menu system ou sur un bouton Fermer d'une fenêtre enfant.

HTTOP

Sur le bord horizontal supérieur d'une fenêtre

HTTOPLEFT

Sur le coin supérieur gauche d'une fenêtre

HTTOPRIGHT

Sur le coin supérieur droit d'une fenêtre

HTTRANSPARENT

Sur une fenêtre recouverte par une autre

HTVSCROLL

Sur un ascenseur vertical

HTZOOM

Sur un bouton de maximisation

Voyons maintenant un exemple de DLL permettant le hook du clavier et de la souris. Celle-ci sera composée de cinq fonctions : la fonction d'initialisation (fermeture) de la DLL qui attachera (détachera) la zone de mémoire partagée, la fonction d'initialisation des hooks, la fonction de destruction des hooks et les deux fonctions de crochetage (clavier et souris).

Voici le code de la DLL. Le fichier compilé sera nommé « hooksClSo.dll ».

#include <windows.h>
// Définition de la structure de la zone mémoire partagée

typedef struct _TDonnees
{
HHOOK MouseHookHandle; // Handle du hook de la souris
HHOOK KeybdHookHandle; // Handle du hook du clavier
HWND hDestWindow; // Handle de la fenêtre à laquelle le hook du clavier doit les données
// Mettez ici toutes les données que vous voulez partager
} TDonnees;

// Déclaration des variables globales de la DLL
HANDLE MemPartagee; // Handle de la zone de mémoire partagée
TDonnees * VueDonnees; // Pointeur vers la zone de mémoire
HINSTANCE HInst; // Handle d'instance de la DLL

// Déclaration des fonctions de la DLL
void _export InitHook(HWND hDest);
void _export EndHook();
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK KeybdProc(int nCode,WPARAM wParam,LPARAM lParam);

// fonction d'initialisation de la DLL
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
HInst = hinst;
switch (reason)
{
case DLL_PROCESS_ATTACH : // à la création de l'instance de la DLL
// Attachement d'une zone de mémoire partagée (notez le cast)
MemPartagee = CreateFileMapping((HANDLE)0xFFFFFFFF, // On map un espace mémoire
NULL, // Pas d'informations de sécurité
PAGE_READWRITE, // Partage en lecture/écriture
0, // Taille de la zone réservée sur 64 bits (32 bits de poids fort)
sizeof(TDonnees), // 32 bits de poids faible
"Tutoriel Hooks par gRRosminet"); // Nom de la zone réservée

// Création d'une vue pour pouvoir accéder à la zone de mémoire partagée (notez le cast)
VueDonnees = (TDonnees *)(MapViewOfFile((HANDLE)MemPartagee, // Zone sur laquelle créer la vue
FILE_MAP_WRITE, // Mode d'accès en lecture/écriture
0, 0, // Offset de début de la vue sur 64 bits
0)); // Taille de la vue (0 = tout)
break;

case DLL_PROCESS_DETACH : // au détachement de la DLL
// Destruction de la vue sur la zone de mémoire partagée
UnmapViewOfFile((LPVOID)VueDonnees);
// Détachement de la zone de mémoire partagée
CloseHandle(MemPartagee);
break;

default :
// DLL_THREAD_ATTACH, DLL_THREAD_DETACH
break;
}
return 1;
}

void _export InitHook(HWND hDest) // _export est spécifique à BCB
{
// Installation du hook sur la souris
VueDonnees->MouseHookHandle = SetWindowsHookEx(WH_MOUSE, // Créer un hook sur la souris
(HOOKPROC)MouseProc, // Utiliser la fonction MouseProc
HInst, // Dans la DLL d'instance HInst
0); // Pour tous les threads

// Installation du hook pour le clavier
VueDonnees->KeybdHookHandle = SetWindowsHookEx(WH_KEYBOARD, // Créer un hook sur le clavier
(HOOKPROC)KeybdProc, // Utiliser la fonction KeybdProc
HInst, // Dans la DLL d'instance HInst
0); // Pour tous les threads

// Partage de la fenêtre destinatrice des données du clavier
VueDonnees->hDestWindow = hDest;
}

void _export EndHook()
{
// Supression des hooks
UnhookWindowsHookEx(VueDonnees->MouseHookHandle);
UnhookWindowsHookEx(VueDonnees->KeybdHookHandle);
}

LRESULT CALLBACK _export MouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
// On joue un son à chaque fois que l'utilisateur enfonce le bouton gauche
if (wParam == WM_LBUTTONDOWN)
MessageBeep(MB_OK);
return CallNextHookEx(VueDonnees->MouseHookHandle,nCode,wParam,lParam);
}

#define WMAP_KEYBDHOOKMSG WM_APP + 1

LRESULT CALLBACK _export KeybdProc(int nCode,WPARAM wParam,LPARAM lParam)
{
// On envoie un message WMAP_KEYBDHOOKMSG à chaque fois que
// l'utilisateur presse la touche ENTRÉE
if (wParam == VK_RETURN)

PostMessage(VueDonnees->hDestWindow, WMAP_KEYBDHOOKMSG, 0, 0);

return CallNextHookEx(VueDonnees->KeybdHookHandle,nCode,wParam,lParam);
}

Voyons maintenant l'application. Tout d'abord, créons une fiche qui contient une case à cocher et un label comme le montre l'image ci-contre. Ensuite, il va falloir définir un gestionnaire de message pour WMAP_KEYBDHOOKMSG ainsi que quelques variables nécessaires à l'application. Ci-après, l'entête de la fiche.

#ifndef appliH
#define appliH

#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>

typedef void (*TInitFunc)(HWND);
typedef void (*TEndFunc)();

#define WMAP_KEYBDHOOKMSG WM_APP + 1

class TForm1 : public TForm
{
__published: // Composants gérés par l'EDI
TCheckBox *CheckBox1;
TLabel *Label1;
void          __fastcall CheckBox1Click(TObject *Sender);
void          __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private: // Déclarations utilisateur
TInitFunc InitHooks; // fonction d'initialisation des hooks
TEndFunc EndHooks; // fonction de suppression des hooks
HINSTANCE hinstDLL; // instance de la DLL
bool bHook; // Les hooks ont-ils été initialisés ?
int nbEntree; // compteur de pressions pour le gestionnaire de messages
public: // Déclarations utilisateur
void __fastcall PressionEntree(TWMNoParams &p);
__fastcall TForm1(TComponent* Owner);
protected: // déclaration du gestionnaire de messages
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(WMAP_KEYBDHOOKMSG, TWMNoParams, PressionEntree)
END_MESSAGE_MAP(TForm)
};

extern PACKAGE TForm1 *Form1;

#endif

Voyons maintenant du côté du source :

#include <vcl.h>
#include "appli.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;

__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
bHook = false; // Les hooks ne sont pas initialisés
hinstDLL = LoadLibrary("hooksClSo.dll"); // Chargement de la librairie
if (!hinstDLL) // Erreur lors du chargement de la librairie ?
Application->MessageBox("Impossible de charger la librairie.","gloups",MB_OK);
else
{
// On récupère les adresses des fonctions
InitHooks = (TInitFunc)GetProcAddress(hinstDLL, "_InitHook");
EndHooks = (TEndFunc)GetProcAddress(hinstDLL, "_EndHook");
}
}

void __fastcall TForm1::CheckBox1Click(TObject *Sender)
{
if (!hinstDLL) // La librairie n'est pas chargée, inutile de continuer
Close();
else if (CheckBox1->Checked) // Activer les hooks
{
nbEntree = 0; // On réinitialise le compteur
InitHooks(Form1->Handle); // On initialise les hooks
}
else // Désactiver les hooks
EndHooks(); // On supprime les hooks
}

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
if (bHook) // Si les hooks sont actifs, on les supprime avant de quitter
EndHooks();

Action = caFree;
}

void __fastcall TForm1::PressionEntree(TWMNoParams &p)
{
nbEntree++; // On a reçu un message du hook : on incrémente et on affiche le compteur
Label1->Caption = AnsiString("Pressions de ENTREE : ") + nbEntree;
}

Une fois compilé, cet exemple permettra de compter les pressions et relâchements de la touche ENTREE, et jouera le son « par défaut » de Windows à chaque clic de souris.