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 cibleRécupérer le handle de la fenêtre au premier plan et envoyer le messageLes différents messages pour le clavier et la souris.
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éciseRécupérer le handle d'une fenêtre précise ou envoyer le message à la fenêtre au premier planRécupérer le handle de 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 envoyerLes différents messages pour le clavier et la souris et comment l'envoyerComment envoyer un message.
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 clavierLe clavier dans tous ses états, puis nous verrons que le fonctionnement de la sourisLes poules picorent, la souris cliquette 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édenteLe clavier :. 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\n
Le class name de cette fenêtre est :
\r\n
|%s|
"
,szBuffer);
printf
(
"
\r\n
Les 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 hookQu'est-ce qu'un hook ?, les différents types de hooks que propose WindowsLes différents types de hooks, les prérequis pour la mise en place d'un hookPrérequis à la mise en place d'un hook, puis comment les mettre en placeMise en place d'un hook.
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.