I. Les messages sous Windows▲
Rappelons brièvement que Windows est un système basé sur le déclenchement d'événements. Il y a un échange permanent de messages divers entre le système d'exploitation et les processus actifs (applications en cours d'exécution). La plus grande majorité de ces messages est déclenchée par une intervention utilisateur (mouvement souris, entrée clavier, etc.). Chaque processus est « à l'écoute » des messages qui peuvent lui parvenir. Cela s'appelle la boucle de message (Message Loop). Plusieurs messages pouvant arriver successivement et rapidement, il existe ce que l'on appelle une « queue de messages » (Message Queue). Vous aurez compris que chaque message est traité dans l'ordre d'arrivée, du plus ancien au plus récent.
Comment cela fonctionne-t-il ? Sans entrer dans des détails complexes, disons simplement qu'une application Windows traditionnelle possède une fonction winmain (le cœur) dans laquelle est codée cette fameuse boucle de message, qui continue à tourner tant que des messages sont censés lui parvenir. Chaque message reçu est traité selon sa fonction prédéfinie. Imaginez un entrepôt de marchandises diverses avec une personne à la réception. Toute marchandise arrivant à la porte de l’entrepôt est examinée et dirigée vers son lieu de stockage ou de fonction. Cette boucle réalise exactement la même chose.
Alors, si une application reçoit des messages du système (et des autres processus), nous allons pouvoir faire communiquer deux processus entre eux, justement par l'envoi de messages. Dans le système, il existe une grande quantité de messages prédéfinis par des constantes. La plus connue de ces constantes se nomme WM_USER et sa valeur est de $0400 (1024 en décimal). Windows se réservant les 1024 premières valeurs pour ses messages prédéfinis, il nous reste toutes les valeurs supérieures à 1024 (WM_USER + n) pour définir nos propres constantes. En fait, pas tout à fait… On pourrait se dire :
« Puisque Windows garde les 1024 premières valeurs, je vais utiliser la constante WM_MON_MESSAGE_A_MOI avec la valeur 1025 ! »
Ce serait une erreur ! Pourquoi ? Et bien, parce que les fonctions SendMessage, PostMessage et autres peuvent envoyer un message à toutes les applications en cours d'exécution. C'est ce qu'on appelle le broadcast. Imaginez qu'un programmeur ait déjà utilisé cette valeur pour une application et que vous exécutiez cette application en même temps que la vôtre ! Quand vous utiliserez le message WM_MON_MESSAGE_A_MOI, votre programme le recevra, mais l'autre programme le pourrait aussi. Si la réception du message numéro 1025 par l'autre programme demande la fermeture de Windows, vous imaginez le résultat ?
Nous allons utiliser une API qui va nous fournir un numéro de message unique. Il s'agit de RegisterWindowMessage. Comment l'utiliser ? Il suffit de lui passer en paramètre une chaîne de caractères pour qu'elle nous fournisse une valeur unique pour le système. Une fois cette valeur définie, elle sera utilisable jusqu'à l’arrêt du système. Par exemple, pour obtenir une valeur unique pour WM_MON_MESSAGE_A_MOI, nous pourrions écrire :
WM_MON_MESSAGE_A_MOI := RegisterWindowMessage('WM_MON_MESSAGE_A_MOI'
);
J'ai repris le nom de la variable sous forme de chaîne, mais cela n'est pas une obligation. Vous pourriez tout aussi bien écrire :
WM_MON_MESSAGE_A_MOI := RegisterWindowMessage('Je veux un numéro de message unique'
);
Cela fonctionnera tout aussi bien.
L'envoi des messages ainsi créés se fait principalement par l'intermédiaire des deux API SendMessage et PostMessage. Quelle différence entre les deux ? Souvenez-vous de notre fameuse boucle de message ! Je vous disais que le traitement se faisait de façon séquentielle. Et bien SendMessage envoie le message, l'ajoute à la queue de messages, attend que le message soit transmis et ensuite seulement renvoie un résultat. PostMessage envoie le message et renvoie immédiatement un résultat sans attendre la transmission du message (son passage dans la boucle d'attente).
L'envoi de message étant expliqué, il nous reste à voir comment recevoir un message bien précis. Pour cela, deux solutions ! Soit on crée une procédure qui ne recevra QUE le message désiré, soit on surcharge la procédure qui s'occupe de tous les messages non traités par la fiche, et on filtre les messages selon leur fonction. Il s'agit de la procédure DefaultHandler que l'on peut surcharger. Que dit l'aide Delphi à ce propos ?
DefaultHandler est une procédure de TCustomForm qui gère tous les messages que la fiche n'a pas entièrement traités. procedure DefaultHandler(var Message); override;
Description
Surchargez la méthode DefaultHandler pour modifier le traitement par défaut des messages de la fiche. Ceci est rarement nécessaire, car les messages peuvent être gérés en créant des méthodes de message.
DefaultHandler transmet tous les messages non gérés à la procédure de fenêtre de la fiche en appelant la fonction CallWindowProc de l'API.
Remarque : l'appel de la méthode héritée dans une méthode de gestion des messages produit l'appel de la méthode DefaultHandler de l'ancêtre si celui-ci ne spécifie pas de gestionnaire pour le message à gérer.
Pour notre exemple, elle nous sera nécessaire à cause de l'utilisation de RegisterWindowMessage. La création d'une procédure pour gérer un message unique est simple. La déclaration doit se faire dans la section public de la TForm. Par exemple, pour notre valeur précédente, on écrira :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
const
WM_MON_MESSAGE_A_MOI : integer
= WM_USER + 1
;
procedure
WmMonMessageAmoi(var
M: TMessage); message
WM_MON_MESSAGE_A_MOI;
implementation
procedure
WmMonMessageAmoi(var
M: TMessage);
begin
// Ici, on ne traite QUE le message WM_MON_MESSAGE_A_MOI;
end
;
Vous avez remarqué que WM_MON_MESSAGE_A_MOI doit être une constante. À cause de cela, nous utiliserons la procédure DefaultHandler plutôt qu'une gestion de message au coup par coup. Pourquoi ? Tout simplement parce que la valeur de WM_MON_MESSAGE_A_MOI ne sera connue qu'au lancement du programme par l'utilisation de RegisterWindowMessage. Il est donc impossible de définir sa valeur lors de la conception du code.
Tout au long de ce document, j'utiliserai SendMessage. Alors, voyons comment utiliser cette API pour un simple échange de données entre applications ! Pour illustrer ce travail, nous allons créer deux projets, l'un sera l'émetteur et l'autre le receveur.
Dans nos deux projets, nous allons définir (pour plus de clarté) les mêmes noms pour nos variables identificateur de message. Vous noterez que l'utilisation de la même chaîne de caractères renverra la même valeur numérique à nos deux programmes, nous permettant de connaître ces valeurs sans autre recherche.
IMPORTANT : (pour trouver le handle de chaque application)
La fiche de l'application Emetteur aura comme titre : « Cette appli envoie les infos au receveur ».
La fiche de l'application Receveur aura comme titre : « Cette appli reçoit les infos ».
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
implementation
{$R *.DFM}
var
WM_MESSAGE_ENVOYEUR, WM_ENVOI_ATOM: integer
;
procedure
TForm1.FormCreate(Sender: TObject);
begin
WM_MESSAGE_ENVOYEUR := RegisterWindowMessage('WM_MESSAGE_ENVOYEUR'
);
WM_ENVOI_ATOM := RegisterWindowMessage('WM_ENVOI_ATOM'
);
end
;
Dans notre receveur, nous allons surcharger DefaultHandler.
2.
3.
4.
5.
6.
public
procedure
DefaultHandler(var
msg); override
;
begin
inherited
DefaultHandler(Msg);
end
;
Voilà ! À ce stade, nos deux applications peuvent dialoguer. Ou plus exactement, notre Receveur peut récupérer des messages, y compris ceux de notre émetteur.
II. Envoi d'un nombre▲
Le cas le plus simple de besoin d'échange de données est l'échange d'une valeur entière. Les paramètres de SendMessage sont respectivement la handle de l'application qui devra recevoir le message, le numéro du message, et deux valeurs entières.
Sur la fiche de notre Emetteur, nous allons placer un TEdit et un TButton. Bridons notre TEdit pour imposer la saisie de valeur entière uniquement en modifiant l'événement OnKeyPress, et modifions l'événement OnClick du TButton pour envoyer notre valeur numérique. Pour envoyer notre message, nous devons connaître le handle du programme Receveur. Nous le trouverons en utilisant l'API FindWindow.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
procedure
TForm1.Edit1KeyPress(Sender: TObject; var
Key: Char
);
var
S: string
;
err, valeur: integer
;
begin
if
Key in
['0'
..'9'
, #8
] then
begin
S := Edit2.text + Key;
val(S, Valeur, err);
if
err <> 0
then
Key := #0
;
end
else
Key := #0
;
end
;
end
;
procedure
TForm1.Envoyer1Click(Sender: TObject);
var
h: THandle;
begin
{Envoie au receveur le nombre indiqué dans Edit1.text}
if
Edit1.text = ''
then
ShowMessage('Veuillez entrer un nombre '
)
else
begin
h := FindWindow(nil
, PChar('Cette appli reçoit les infos'
));
if
h = 0
then
ShowMessage('Le receveur est inactif'
)
else
SendMessage(h, WM_MESSAGE_ENVOYEUR, StrToInt(Edit1.text), 0
);
end
;
end
;
Que va-t-il se passer dans notre Receveur ? Pour mieux le montrer, rajoutons à notre fiche Receveur une un TLabel pour y inscrire le résultat.
2.
3.
4.
5.
procedure
TForm1.DefaultHandler(var
msg);
begin
inherited
DefaultHandler(Msg);
if
TMessage(msg).Msg = WM_MESSAGE_ENVOYEUR then
Label1.Caption := IntToStr(TMessage(msg).WParam);
end
;
Si DefaultHandler reçoit le message WM_MESSAGE_ENVOYEUR, il affiche la valeur de WParam (valeur passée par le message).
III. Envoi d'une chaîne de caractères▲
Pour illustrer l'envoi d'une chaîne de caractères, nous allons parler des atom. Aucune radiation ni reste de Tchernobyl là-dedans, rassurez-vous ! Un atom n'est rien qu'une string[255 {maxi}] avec un identifiant numérique stocké soit au niveau du processus, soit au niveau global du système. Chaque atom peut donc être retrouvé avec son identificateur numérique. La table est globale et peut être partagée par tous.
Pour transmettre une chaîne, il va donc nous suffire d'ajouter un atom global au système et d'envoyer son identifiant numérique au Receveur qui, à partir de ça, pourra retrouver la chaîne dans la table. Pour l'exemple, utilisons un TEdit pour la saisie du texte et un TButton pour l'envoi du message. Pour ajouter un atom à la table globale, nous utiliserons GlobalAddAtom.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
procedure
TForm1.Envoyer2Click(Sender: TObject);
var
h: THandle;
atom_Envoye: Atom;
begin
if
length(edit2.text) = 0
then
ShowMessage('Vous devez saisir un texte à envoyer'
)
else
{Création d'un atom dans la table globale
Notification par message au receveur de l'envoi d'un atom}
begin
atom_Envoye := GlobalAddAtom(PChar(edit2.text));
h := FindWindow(nil
, PChar('Cette appli reçoit les infos'
));
if
h = 0
then
ShowMessage('Le receveur est inactif'
)
else
SendMessage(h, WM_ENVOI_ATOM, atom_Envoye, 0
); {l'atom est envoyé dans WParam}
end
;
end
;
Que va-t-il se passer dans notre receveur ? Et bien nous allons filtrer le nouveau message possible et agir en conséquence.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
procedure
TForm1.DefaultHandler(var
msg);
begin
inherited
DefaultHandler(Msg);
if
TMessage(msg).Msg = WM_MESSAGE_ENVOYEUR then
Label1.Caption := IntToStr(TMessage(msg).WParam)
else
if
TMessage(msg).Msg = WM_ENVOI_ATOM then
begin
// le numéro identifiant l'atom se trouve dans WParam
atom_recu := TMessage(msg).WParam;
GetMem(TexteRecu, 256
); {255 maxi + #0}
GlobalGetAtomName(atom_recu, TexteRecu, 256
);
label5.caption := TexteRecu;
GlobalDeleteAtom(atom_recu); {Ne pas oublier de détruire l'atom puisqu'on a récupéré la valeur}
FreeMem(TexteRecu);
end
;
end
;
IV. Envoi d'une structure▲
Nous allons terminer cette mini revue de l'utilisation des messages avec celui qui permet de transmettre une structure, et donc, tout et n'importe quoi. Le message utilisé sera WM_COPYDATA. Puisque ce message fait partie de ceux prédéfinis par Windows, nous allons pouvoir créer dans notre Receveur une procédure qui s'occupera principalement de lui. Pour l'exemple, j'ai ajouté sur l'Emetteur et le Receveur les éléments suivants :
L'émetteur transmettra sous forme de structure les différentes infos de ce TPanel. Là, le traitement est un peu plus compliqué. Les paramètres sont : handle de l'application Receveur, le message WM_COPYDATA, le handle de l'application émettrice, un pointeur sur une structure de type TCopyDataStruct.
2.
3.
4.
5.
TCopyDataStruct = record
dwData: LongInt
; {une valeur au choix de l'utilisateur}
cbData: LongInt
; {taille de la structure de données envoyée}
lpData: Pointer
; {pointeur sur la structure}
end
;
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
implémentation
type
PCopyDataStruct = ^TCopyDataStruct;
TCopyDataStruct = record
dwData: LongInt
;
cbData: LongInt
;
lpData: Pointer
;
end
;
type
PEnvoiFax = ^TEnvoiFax;
TEnvoiFax = packed
record
Destinataire: string
[255
];
TypeDocument: integer
;
DateEnvoi: TDateTime;
EnvoiFait: boolean
;
end
;
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
procedure
TForm1.Envoyer3Click(Sender: TObject);
var
h: THandle;
CopyDataStruct: TCopyDataStruct;
Envoi: TEnvoiFax;
begin
{Envoyer une structure de données avec WM_COPYDATA}
h := FindWindow(nil
, PChar('Cette appli reçoit les infos'
));
if
h = 0
then
ShowMessage('Le receveur est inactif'
)
else
begin
with
Envoi do
begin
Destinataire := Edit1.text;
TypeDocument := RadioGroup1.ItemIndex;
DateEnvoi := DateTimePicker1.DateTime;
EnvoiFait := CheckBox1.Checked;
end
;
with
CopyDataStruct do
begin
dwData := 1
;
cbData := sizeof(Envoi);
lpData := @Envoi;
end
;
SendMessage( (h, WM_COPYDATA, Form1.Handle, LongInt
(@CopyDataStruct));
end
;
end
;
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public
procedure
WmCopyData(var
M: TMessage); message
WM_COPYDATA;
implémentation
procedure
TForm1.WmCopyData(var
M: TMessage);
begin
with
PEnvoiFax(PCopyDataStruct(m.LParam)^.lpData)^ do
begin
Edit1.text := Destinataire;
RadioGroup1.ItemIndex := TypeDocument;
DateTimePicker1.DateTime := DateEnvoi;
CheckBox1.Checked := EnvoiFait;
end
;
end
;
V. Conclusion▲
Comme vous le voyez, l'échange de données est une chose simple. Comme je le précisais au début de cet exposé, il existe bien d'autres méthodes, mais les trois décrites ici suffisent le plus souvent à régler tous les problèmes d'échange.
À bientôt pour une autre aventure !
Source D5