Comment gérer les messages système depuis Delphi.
Date de publication : 12/10/2006 , Date de mise à jour : 12/10/2006
Par
Determe Jean-François
Ce tutoriel explique comment gérer les messages système avec Delphi à travers 3 projets.
I. Introduction
II. Objectif de ce tutoriel
III. Structure générale d'un message système
IV. Détection d'un évènement clavier
V. Communication élémentaire entre applications
VI. Communication évoluée entre applications
I. Introduction
Les applications Windows sont du type évènementiel,
c'est à dire qu'elles attendent patiemment que le système leur transmette des messages et réagissent en
conséquence. Un message est un ensemble d'informations qu'un emetteur envoie à un ou plusieurs destinataires
pour que ces derniers y réagissent (mais ils sont libres de ne rien faire). Ils peuvent être envoyés par le système
(signaler l'appui sur une touche...), par une autre application, ou tout simplement au sein du même du processus
(typiquement un contrôle génère un nouveau message en réponse à un autre message).
C'est donc un mécanisme de communication inter-processus, qui permet de communiquer avec le système mais aussi avec une autre application, comme nous allons le voir dans cet article.
II. Objectif de ce tutoriel
Apprendre comment intercepter un mouvement de souris, une touche enfoncée au clavier,etc.
Mais également apprendre à faire communiquer 2 applications distinctes grâce à des messages définis par le programmeur.
On ne s'attardera pas sur la théorie mais plutôt sur la pratique.
En fin de chapitre, nous construirons un système dans lequel plusieurs applications échangent des messages ,
cet exemple nous permettra de découvrir quelques subtilités de l'utilisation des messages système.
III. Structure générale d'un message système
Type
Tmsg = packed record
hwnd: HWND; // Identificateur de la fenêtre destinataire
message: Uint; // Identificateur du message
wParam: WPARAM; // Variable contenue dans le message ( Du type pointeur ou entier signé sur 32 bits )
lParam: LPARAM; // Variable contenue dans le message ( Du type pointeur ou entier signé sur 32 bits )
time: DWORD; // Moment de la création du message
pt: Tpoint; // Position du pointeur souris à la création du
// message
end; |
 |
Dans le cadre de ce tutoriel, on n'utilisera que les variables hwnd, message,
wParam et Lparam.
|
IV. Détection d'un évènement clavier
Une manière simple de réaliser cette opération serait d'utiliser la méthode :
TVotreForm.OnKeyPress(Sender: TObject; var Key: Char) |
Dans le code source du composant TForm l'évènement OnKeyPress est en fait
géré grâce aux messages système. ( Le message wm_char pour être précis )
Dans le cas de où on gère la pression sur
une touche du clavier grâce au composant VCL TForm, le code ressemblera à ceci :
procedure TMainFrame.FormKeyPress(Sender: TObject; var Key: Char);
begin
showmessage('Vous avez appuyé sur une touche du clavier');
end; |
On peut également afficher la touche pressée grâce à la variable Key.
Dans notre cas nous allons utiliser nos propres messages système ( Nous faisons ceci à but didactique ).
Avant de nous attaquer au programme, il convient de connaître la structure de
l'entité TWMCHAR dont vous trouverez une description générale :
Type
TWmChar = packed record
msg: Cardinal; // Code ( Integer ) du message ( pour le message
// WM_CHAR : 258 ), ce code restera constant.
CharCode: Word; // Code ASCII du caractère frappé ou touche
// virtuelle ( EX. VK_CANCEL ).
Result: Integer; // Valeur renvoyée après réception du message
// par l'application traitant le message.
KeyData: Integer; // La description de l'aide Borland étant
// obscure à ce sujet, je ne fait pas de
// description sur cette variable.
end; |
Voici le code source de l'unité Main disponible dans le fichier en téléchargement :
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMainFrame = class(TForm)
Label1: TLabel;
Label2: TLabel;
private
{ Déclarations privées }
procedure WMCHAR(var Msg: TWMCHAR); message WM_CHAR;
public
{ Déclarations publiques }
end;
var
MainFrame: TMainFrame;
implementation
{$R *.dfm}
procedure TMainFrame.WMCHAR(var Msg: TWMCHAR);
var c: string; // Message figurant dans le Label2 une fois le message reçu
begin
c := 'Message reçu, la touche sur laquelle vous avez appuyée est : ' + chr(Msg.CharCode);
MainFrame.Label2.Caption := c;
end;
end. |
Etudions les déclarations une à une :
private
{ Déclarations privées }
procedure WMCHAR(var Msg: TWMCHAR); message WM_CHAR; |
On déclare donc dans la partie private une procédure que l'on nommera en fonction de
l'identificateur du message (ici WM_CHAR) mais sans le caractère '_'.
On suivra la même règle de nommage pour le type de la variable Msg, en le préfixant par T : TWMChar.
On indique le mot réservé "message" suivi de l'identificateur du message ciblé, ici
WM_CHAR.
Toutes ces directives sont des conventions d'écriture.
Voici la méthode qui traite les messages 'WM_CHAR' lorsque ils surviennent :
procedure TMainFrame.WMCHAR(var Msg: TWMCHAR);
var c: string;
begin
c := 'Message reçu, la touche sur laquelle vous avez appuyée est : ' +
chr(Msg.CharCode);
MainFrame.Label2.Caption := c;
end; |
La déclaration de la procédure dans la partie implementation est semblable, il y a
juste 2 points qui diffèrent :
- On ajoute le nom de la fiche précédé du caractère T
- On ne doit plus indiquer la directive "message WM_CHAR" après la déclaration
de la procédure. ( Le compilateur devine de quoi il s'agit )
La variable Msg.charcode contient le code ASCII du caractère frappé. Pour pouvoir
affecter sa valeur à une variable de type string on utilisera la fonction chr().
Pour rappel, la fonction chr() attend un argument de type integer qui correspond
au code ASCII d'un caractère et retourne une valeur de type char.
Notez que la variable Msg.charcode ne sera pas toujours valide ( en ce sens qu'elle n'aura pas d'existence pour certains messages windows ), cela dépend du type de message qu'on souhaite gérer.
Par exemple cette variable n'existera pas pour un message de type WM_MouseMove.
Vous savez maintenant comment détecter si l'utilisateur presse une touche du
clavier sans utiliser le gestionnaire d'évènement inclus dans un composant TForm.
V. Communication élémentaire entre applications
Dans ce point important du tutoriel, nous allons apprendre comment faire
dialoguer 2 applications distinctes à l'aide d'un message système défini par vous-même.
Voici le code de l'application qui va envoyer un message vers toutes les autres
applications en cours d'exécution :
unit Appli1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Envoyer: TButton;
EnvoyeretFermer: TButton;
procedure EnvoyeretFermerClick(Sender: TObject);
procedure EnvoyerClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
MessageSys: UINT;
implementation
{$R *.dfm}
procedure TForm1.EnvoyerClick(Sender: TObject);
begin
MessageSys := RegisterWindowMessage('MessageUB45'); // On crée un message valide
SendMessage(HWND_BROADCAST,MessageSys, 0, 0); // On envoie ce message aux applications
// qui tournent.
end;
procedure TForm1.EnvoyeretFermerClick(Sender: TObject);
begin
MessageSys := RegisterWindowMessage('MessageUB45');
SendMessage(HWND_BROADCAST,MessageSys, 1, 0);
end;
end. |
Étudions la première procedure ( méthode en toute rigueur ) :
procedure TForm1.EnvoyerClick(Sender: TObject);
begin
MessageSys := RegisterWindowMessage('MessageUB45');
SendMessage(HWND_BROADCAST,MessageSys, 0, 0);
if MessageSys = 0 then
begin
raise Erreur.Create('Erreur lors de l''envoi du message');
end;
end; |
On utilise la variable MessageSys déclarée comme suit :
Cette variable va en fait contenir notre message.
La fonction RegisterwindowMessage() permet de créer un message qui pourra
être envoyé à toutes les applications ( process ) en cours d'exécution. L'argument de cette fonction est
une variable de type string qui va contenir le "nom" ( l'identificateur ) de notre message.
Cette fonction garantit l'unicité, elle retourne une variable de type integer.
Si cette variable vaut 0, la fonction a échoué, pour plus d'informations utilisez
l'aide Delphi en recherchant la fonction RegisterwindowMessage.
On utilise un nom de message un peu spécial pour réduire les chances qu'un autre
programme l'utilise et ainsi on évite de compromettre le bon fonctionnement des
applications extérieures aux nôtres.
Une fois notre message créé on le diffuse à toutes les applications en cours
d'exécution grâce à la fonction SendMessage() ( ou PostMessage() ) définie comme suit :
function SendMessage(hwnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM) : LRESULT; stdcall; |
 |
Si vous désirez en apprendre plus sur les fonctions SendMessage() et Postmessage(),
consultez l'aide Delphi.
|
- hwnd est l'identificateur de la fenêtre concernée ou une valeur spéciale pour un broadcast.
- Msg est l'identificateur du message.
- wParam et lParam sont des données supplémentaires que nous pouvons
ajouter avec le message, elles sont des données de type integer .
On passe comme argument pour hwnd HWND_BROADCAST pour faire en sorte
que le message soit envoyé à toutes les applications en cours d'exécution.
Étudions maintenant la seconde procédure :
procedure TForm1.EnvoyeretFermerClick(Sender: TObject);
begin
MessageSys := RegisterWindowMessage('MessageUB45');
SendMessage(HWND_BROADCAST,MessageSys, 1, 0);
if MessageSys = 0 then
begin
raise Erreur.Create('Erreur lors de l''envoi du message');
end;
end; |
Elle est pratiquement identique à la première étudiée sauf pour un point : on passe
comme argument pour la variable wParam l'entier 1.
Pour comprendre l'intérêt de cette différence, étudions le programme qui va
analyser le message ainsi reçu.
Voici le code source du programme récepteur :
unit Appli2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Label1: TLabel;
procedure FormCreate(Sender: TObject);
private
{ Déclarations privées }
procedure DefaultHandler(var Msg); override;
public
{ Déclarations publiques }
end;
var
Form2: TForm2;
MessageSys : UINT; // Message recherché
implementation
{$R *.dfm}
procedure TForm2.DefaultHandler;
begin
inherited DefaultHandler(Msg);
if (TMessage(Msg).Msg=MessageSys) then
begin
if (TMessage(Msg).WParam) = 1 then
begin
Application.Terminate; // On ferme l'application
end
else
Form2.Label1.Caption := 'Message reçu';
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
MessageSys := RegisterWindowMessage('MessageUB45');
end;
end. |
Pour la réception du message on utilise la méthode defaulthandler(), qui
traite tous les messages qui ont pour destination votre application.
Regardons la procédure Defaulthandler :
procedure TForm2.DefaultHandler;
begin
inherited DefaultHandler(Msg);
if (TMessage(Msg).Msg=MessageSys) then
begin
if (TMessage(Msg).WParam) = 1 then
begin
Application.Terminate; // On ferme l'application
end
else
Form2.Label1.Caption := 'Message reçu';
end;
end; |
Vous constatez qu'on mentionne inherited; cela permet une propagation du
message au gestionnaire éventuel de l'ancêtre de l'entité en question.
On vérifie si le message reçu est bien le message attendu que l'on a défini lorsque
la fiche a été créée.
Si oui, on effectue une seconde vérification visant à connaître le valeur de
wParam transmise avec le message, si celle-ci égale 1 on termine l'application.
Vous avez maintenant en main tous les outils pour faire dialoguer vos applications entre
elles.
VI. Communication évoluée entre applications
Dans ce chapitre, on va réaliser un système composé de 2 applications
qui vont communiquer entre elles de manière un peu plus "raffinée" que nos
programmes précédents.
Cet exemple montre bien l'utilité des messages système et la manière
de les utiliser.
unit Emetteur;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMain = class(TForm)
Console: TMemo;
btnDemarrer: TButton;
Message: TEdit;
procedure btnDemarrerClick(Sender: TObject);
private
{ Déclarations privées }
public
{ Déclarations publiques }
procedure DefaultHandler(var Msg); override;
end;
var
Main: TMain;
i : integer = 1; // Variable utilisée pour l'incrémentation des messages console
HandleEdit : THandle; // Handle du composant TEdit qui contient le message
// à afficher
MonHandle: THandle; // Handle de l'application émettrice
HandleAppliExt: THandle; // Handle d'une application extérieure
Message1: UINT; // Premier message envoyé ( En mode broadcast )
Message2: UINT; // Premier message reçu
Message3: UINT; // Second message envoyé
implementation
{$R *.dfm}
procedure EcrireConsole(MyText: string); // Procédure qui va écrire dans la console
begin
Main.Console.Lines[i] := MyText;
i := i + 1;
end;
procedure TMain.DefaultHandler; // Méthode qui intercepte les messages
begin
inherited DefaultHandler(Msg);
if (TMessage(Msg).Msg=Message2) then
begin
HandleAppliExt := (TMessage(Msg).WParam);
EcrireConsole('Un message a été reçu'); // Premier message reçu
Message3 := RegisterWindowMessage('SX_MessageSys4591'); // On enregistre le
// troisième message.
EcrireConsole('3ème message enregistré');
SendMessage(HandleAppliExt, Message3, HandleEdit, 0); // On envoie le troisième message
end;
end;
procedure TMain.btnDemarrerClick(Sender: TObject); // Méthode de démarrage
begin
Main.Console.Enabled := true;
HandleEdit := Main.Message.Handle; // On définit le handle de l'Edit qui contient
// le message affiché par l'application réceptrice.
MonHandle := Main.Handle; // On encode le handle de la fenêtre de l'application
// émettrice.
{ On encode les messages }
Message1 := RegisterWindowMessage('SX_MessageSys4589');
EcrireConsole('1er message enregistré');
Message2 := RegisterWindowMessage('SX_MessageSys4590');
EcrireConsole('2eme message enregistré');
EcrireConsole('Message en attente de réception encodé');
SendMessage(HWND_BROADCAST,Message1, MonHandle, 0); // Envoi du premier
// message.
end;
end. |
Le programme émetteur, envoie à toutes les applications
en cours d'exécution un message qui contient le handle de sa fenêtre
Si l'application émettrice reçoit un message venant d'une des applications
réceptrices ( le message en question étant contenu dans la variable "Message2" ),
il en extrait le handle de l'application émettrice en question ( de la fenêtre en réalité) et renvoie
un message contenant le handle du champ TEdit qui contient un message qui va être affiché par les applications réceptrices mais
cette fois-ci, en envoyant le message uniquement à la fenêtre concernée et non en mode broadcast.
 |
Notez que l'on peut passer dans les variables WParam et LParam un argument
de type THandle ( Le handle étant finalement une variable entière ).
|
unit Recepteur;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TEditTest = class(TEdit)
end;
type
TMain = class(TForm)
Label1: TLabel;
Label2: TLabel;
procedure FormCreate(Sender: TObject);
private
{ Déclarations privées }
procedure DefaultHandler(var Msg); override;
public
{ Déclarations publiques }
end;
var
Main: TMain;
Message1: UINT; // Premier message reçu
Message2: UINT; // Premier message renvoyé
Message3: UINT; // Second message reçu
HandleAppliExt: THandle; // Handle de l'application émettrice.
HandleRecepteur: THandle; // Handle de l'application réceptrice.
MonEdit: THandle; // Edit de l'application émettrice contenant le message à
// afficher
MonTexte: Array [0..255] of Char; // Message à afficher
implementation
{$R *.dfm}
procedure TMain.DefaultHandler;
begin
inherited DefaultHandler(Msg);
if (TMessage(Msg).Msg) = Message1 then
begin
Main.Label1.Caption := 'Message reçu'; // on indique que le premier message est
// reçu.
HandleAppliExt := (TMessage(Msg).WParam); // On encode le handle de la fenêtre
// émettrice
SendMessage(HandleAppliExt, Message2, HandleRecepteur, 0);
end;
if (TMessage(Msg).Msg) = Message3 then
begin
MonEdit := (TMessage(Msg).WParam); // On récupère le handle de l'Edit contenant
// notre message.
SendMessage(MonEdit,WM_GETTEXT,255,integer(@MonTexte)); // On récupère le message
// à afficher
Main.Label2.Caption := Main.Label2.Caption + MonTexte; // On affiche le message
end;
end;
procedure TMain.FormCreate(Sender: TObject);
begin
// On encode tout les messages.
HandleRecepteur := Main.Handle;
Message1 := RegisterWindowMessage('SX_MessageSys4589');
Message2 := RegisterWindowMessage('SX_MessageSys4590');
Message3 := RegisterWindowMessage('SX_MessageSys4591');
end;
end. |
Le programme récepteur, attend la réception d'un
message ( Message1 )qui contient le handle de la fenêtre
émettrice; il retourne ensuite un autre message contenant le
handle de sa fenêtre à l'application émettrice et cela sans utiliser le mode
broadcast ( càd en utilisant le handle de la fenêtre émettrice récupéré auparavant).
Notre application réceptrice reçoit ensuite un autre message venant de
l'application émettrice; dans ce message est inclus le handle du champ
TEdit qui contient notre message.
On renvoie ensuite un message prédéfini par Windows ( normal : on observe le
suffixe WM_NomDuMessage ) au handle du champ TEdit, ce message place dans
la string "MonTexte" le message à afficher, pour des questions de compatibilité,
la string est définie comme une array mais si on y réfléchit, une variable de type string a
la même structure que notre array.
Si vous désirez plus d'information sur le message WM_GETTEXT, consultez
l'aide Delphi.
Sachez également que si vous développez des composants VCL, une maitrise des messages système
s'impose pour gérer les évènements tels qu'une pression sur une touche au clavier.
Cliquez ici pour laisser vos commentaires sur cet article.
| (1) | Remerciements :
Laurent Dardenne pour ses conseils et la relecture de cet article.
Bernard Determe pour ses conseils en matière de style.
Yann Marec alias ElMilouse pour la relecture de cet article.
Aurelien.regat-barrel pour sa définition des messages système.
Tous les membres de developpez.com qui au travers de leurs posts m'ont
aidé à résoudre certains problèmes.
Un grand merci à la rédaction de developpez.com pour m'avoir fournit
cet espace web et m'avoir aidé à rédiger mon tutoriel.
|


Copyright © Jean-François Determe. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
Cette page est déposée à la
SACD.