Présentation des mailslots et de leur utilisation en Delphi
Date de publication : 27/06/2004
Par
Pierre Castelain
Cet article a pour but de présenter les mailslots et leur utilisation en Delphi.
Les mailslot représentent un moyen simple de faire communiquer différents processus entre eux.
1. Introduction
2. Les adresses
3. Les fonctions
3.1. CreateMailslot
3.2. GetMailslotInfo
3.3. SetMailslotInfo
4. Utilisation en Delphi
4.1. Un exemple simple
4.1.1. Le serveur
4.1.2. Le client
4.2. Allons plus loin
5. Liens
1. Introduction
Dans votre carrière de développeur, vous serez (si ce n'est pas déjà le cas) confronté à un moment ou un autre
au besoin de faire communiquer des applications entre elles. Il existe de nombreuses techniques sous Windows
vous permettant de réaliser cette opérations. Ces techniques sont souvent réunies sous le nom de Communication
Inter-Processus (ou IPC en anglais pour Interprocess Communication). Leur but est de faire transiter une
certaine quantité d'information d'un processus à un autre (le plus souvent des applications).
Les principales techniques disponibles sous Windows sont :
-
le presse-papier,
-
COM,
-
les messages Windows (et en particulier le message WM_COPYDATA),
-
DDE,
-
les fichiers mappés (file mapping),
-
les mailslots,
-
les pipes (anonymes ou nommés),
-
RPC,
-
les sockets Windows.
Il existe d'autres moyens d'arriver à nos fins, comme l'utilisation de fichiers temporaires
ou de la base de registre, mais ce ne sont pas à proprement parler des techniques IPC.
Nous allons nous intéresser aux mailslots. Il s'agit d'une technique très simple d'emploi mais qui peut
rendre de nombreux services.
Pour vous faire une idée plus précise de ce que sont les mailslots, imaginez juste une boîte au lettre
dont seul le propriétaire a la clé. Toutes les personnes connaissant l'adresse de la boîte peuvent y
déposer des messages, mais seul le propriétaire peut les lire. C'est tout simplement comme ça que
fonctionnent les mailslots.
Nous appellerons le propriétaire de la boîte le processus serveur, et les personnes déposant des messages
les processus clients.
Les mailslots sont unidirectionnels, c'est à dire que la communication ne s'effectue que dans un seul sens :
du client vers le serveur. Ceci dit, rien ne vous empêche de d'utiliser dans la même application un
mailslot pour l'envoi et un autre pour la réception.
Les mailslots sont asynchrones, c'est à dire que le client dépose son message et que le serveur le lit
quand il le désire ou quand il en a le temps. Il n'y a donc aucun moyen de savoir si un message a été lu ou non.
Un message ne peut dépasser la taille de 64Ko (ou 425 octets en broadcast, voir plus loin). Si vous voulez faire transiter
des masses de données plus importantes, tournez vous vers une autre technique.
Si vous trouvez que c'est un mauvais début, attendez un peu
Les mailslots présentent certains avantages :
-
Ils ne nécessitent qu'un jeu de fonctions très réduit et simple à utiliser.
-
Ils permettent de faire communiquer simplement des processus s'exécutant sur différents
ordinateurs à travers un réseau.
-
Ils intègrent également un mécanisme de diffusion qui permet à un client d'envoyer un message
à tous les serveurs d'un domaine en un seul appel.
Vous trouvez que cela redevient intéressant ? Alors continuons.
2. Les adresses
Avant de découvrir en détail l'utilisation des mailslots, penchons-nous un moment sur la syntaxe des adresses.
Les adresses des mailslots doivent être construites en suivant certaines règles simples. Ces règles sont
communes au client et au serveur, mais vous n'emploierez pas toujours la même forme pour un client que pour
un serveur.
La forme générale d'une adresse de mailslot est la suivante :
Les parties apparaissant en bleu sont obligatoires et ne peuvent être modifiées. La partie apparaissant
en vert est facultative. Le reste est à votre disposition.
Décomposons cette adresse pour la comprendre :
-
Une adresse de mailslot commence toujours par un double antislash '\\'
-
Vient ensuite le nom de l'emplacement du mailslot. Cet emplacement peut représenter :
-
Un ordinateur : nous utiliserons le nom de l'ordinateur dans le réseau.
-
Tous les ordinateurs d'un domaine : nous utiliserons le nom du domaine (ou du groupe de travail).
-
Tous les ordinateurs du domaine principal : nous utiliserons le caractère étoile '*'.
-
L'ordinateur sur lequel le processus s'exécute : nous utiliserons le caractère point '.'.
Attention : seuls les messages de moins de 425 octets peuvent être envoyés à plusieurs ordinateurs
en même temps (broadcast).
-
L'emplacement est toujours suivi de la chaîne '\mailslot\' qui indique qu'il ne s'agit pas d'un
"vrai" fichier mais bien d'un mailslot.
-
La dernière section est constituée d'un certain nombre de répertoires séparés par des
antislashs '\' et du nom du mailslot. Cette section est donc construite comme une chemin de
fichier classique. Vous pouvez donc classer les mailslots par répertoires si vous en utilisez
beaucoup en même temps ou si vous voulez être rigoureux.
Les clients et le serveur devront utiliser la même adresse pour communiquer à une exception près :
L'emplacement de création du mailslot par le serveur est toujours l'ordinateur local, donc le point '.'.
Un petit exemple :
Imaginons que le serveur s'exécute sur un ordinateur appelé 'SERVEUR' situé sur un domaine appelé 'DOMAINE'
et que le mailslot porte le nom de 'TEST'.
-
Le mailslot sera créé par le processus serveur avec l'adresse : '\\.\mailslot\TEST'.
-
Le mailslot sera ouvert par un client s'exécutant sur la même machine avec l'adresse '\\.\mailslot\TEST'.
-
Le mailslot sera ouvert par un client s'exécutant sur une autre machine avec l'adresse '\\SERVEUR\mailslot\TEST'.
-
Le mailslot sera ouvert par un client désirant envoyer un message à tous les serveurs du domaine
'DOMAINE' avec l'adresse '\\DOMAINE\mailslot\TEST'.
-
Enfin, le mailslot sera ouvert par un client désirant envoyer un message à tous les serveurs du domaine
principal avec l'adresse '\\*\mailslot\TEST'.
Sachez enfin que les adresses de mailslots ne sont pas sensibles à la casse. Les adresses
'\\.\mailslot\test' et '\\.\MAILSLOT\TEST' sont donc équivalentes.
3. Les fonctions
Les mailslots sont implémentés comme des pseudo-fichiers. En clair, cela signifie que les fonctions
permettant d'envoyer ou de lire des messages sont les mêmes que celles habituellement utilisées pour les
fichiers. Seules 3 fonctions leur sont spécifiques :
3.1. CreateMailslot
Cette fonction se charge de créer le mailslot pour le serveur.
Voici sa déclaration en Delphi :
function CreateMailslot(lpName: PChar; nMaxMessageSize: DWORD;
lReadTimeout: DWORD; lpSecurityAttributes: PSecurityAttributes): THandle; stdcall;
Les arguments sont :
-
lpName : le nom du mailslot (son adresse),
-
nMaxMessageSize : la taille maximale d'un message (0 pour une taille quelconque),
-
lReadTimeout : le temps maximal en millisecondes pendant lequel le serveur attendra
un nouveau message avant de quitter le processus de lecture. 0 pour un retour
immédiat s'il n'y a pas de message et MAILSLOT_WAIT_FOREVER pour une attente infinie.
-
lpSecurityAttributes : un pointeur vers une structure de type SECURITY_ATTRIBUTES que
nous ignorerons dans cet article. Sachez simplement que si vous voulez partager le mailslot
avec un processus enfant du serveur, vous devrez modifier le membre bInheritHandle de cette structure.
-
La valeur de retour est un handle (valeur numérique) qui sera utilisé comme argument par
les autres fonctions afin d'identifier le mailslot.
Un appel type en Delphi sera donc de la forme :
h:= CreateMailslot('\\.\mailslot\MonMailslot', 0, MAILSLOT_WAIT_FOREVER, nil);
3.2. GetMailslotInfo
Cette fonction permet de lire les informations concernant le mailslot.
Voici sa déclaration en Delphi :
function GetMailslotInfo(hMailslot: THandle; lpMaxMessageSize: Pointer;
var lpNextSize: DWORD; lpMessageCount, lpReadTimeout: Pointer): BOOL; stdcall;
Les arguments sont :
-
hMailslot : le handle obtenu par l'appel à CreateMailslot.
-
lpMaxMessageSize : pointeur sur un entier qui sera rempli avec la taille maximale d'un message
sur ce mailslot (la valeur fournie à la fonction CreateMailslot).
-
lpNextSize : variable de type entier qui sera remplie avec la taille du prochain message à lire
(MAILSLOT_NO_MESSAGE s'il n'y a pas de message à lire).
-
lpMessageCount : pointeur sur un entier qui sera rempli avec le nombre de messages à lire.
-
lpReadTimeout : pointeur sur un entier qui sera rempli avec le temps maximal en millisecondes
pendant lequel le serveur attendra un nouveau message (valeur fournie à la fonction CreateMailProcess).
-
La valeur de retour est un booléen indiquant si la fonction s'est terminée correctement ou si une
erreur est survenue. En cas d'erreur, consultez GetLastError pour plus d'information sur celle-ci.
Une petite remarque : la déclaration de cette fonction en Delphi est un peu étrange car, en principe,
il est possible de passer la valeur nil à tous les arguments sauf évidemment pour hMailslot. Pourtant,
la déclaration de l'argument lpNextSize en var interdit ce genre d'appel. Je ne sais pas s'il s'agit
d'une erreur ou si c'est volontaire. En tous cas, il nous faudra toujours passer au moins une variable
pour cet argument (à moins de re-déclarer la fonction :-)
3.3. SetMailslotInfo
Cette fonction permet uniquement de modifier la durée maximales en
millisecondes pendant laquelle la lecture attendra un nouveau message. Nous ne nous en servirons pas
dans cet article.
Voici sa déclaration en Delphi :
function SetMailslotInfo(hMailslot: THandle; lReadTimeout: DWORD): BOOL; stdcall;
Les arguments sont :
-
hMailslot : le handle obtenu par l'appel à CreateMailslot.
-
lReadTimeout : entier dont la valeur correspond au temps maximal en millisecondes
pendant lequel le serveur attendra un nouveau message.
-
La valeur de retour est un booléen indiquant si la fonction s'est terminée correctement ou si une
erreur est survenue. En cas d'erreur, consultez GetLastError pour plus d'information sur celle-ci.
C'est tout pour les fonctions spécifiques, voyons maintenant la liste des fonctions communes avec la
gestion des fichiers.
Pour le serveur :
-
CloseHandle,
-
DuplicateHandle,
-
ReadFile et ReadFileEx,
-
GetFileTime,
-
SetFileTime,
-
GetHandleInformation,
-
SetHandleInformation.
Les seules qui nous intéressent réellement sont CloseHandle et ReadFile. Elles vont nous permettre
respectivement de fermer le mailslot et de lire les messages qu'il contient.
Pour le client :
-
CloseHandle,
-
CreateFile,
-
DuplicateHandle,
-
WriteFile et WriteFileEx.
Nous ne nous intéresserons pas aux fonctions DuplicateHandle (qui permet de copier un handle) et
WriteFileEx (qui n'est qu'une version améliorée de WriteFile). Il nous reste donc CloseHandle qui
permettra de fermer la communication avec le mailslot, CreateFile qui permettra d'ouvrir la
communication avec le mailslot et WriteFile que nous utiliserons pour écrire le message.
Je vous laisse le soin de regarder vous même les déclarations de ces fonctions.
4. Utilisation en Delphi
Maintenant que nous savons comment les mailslots fonctionnent, voyons comment les utiliser en Delphi.
4.1. Un exemple simple
Nous allons créer deux applications et les faire communiquer par l'intermédiaire d'un seul mailslot.
4.1.1. Le serveur
Créez une nouvelle application dans Delphi et enregistrez la sous le nom de 'Serveur'.
Enregistrez la fenêtre sous le nom 'ServeurFrm', cela nous évitera de l'écraser plus tard.
Ajoutez un composant TMemo sur la fenêtre et appelez-le 'mmMessages'.
Ajoutez un composant Tbutton sur la fenêtre et appelez-le 'btnLire'.
Dans la section private de la classe de la fenêtre, ajoutez la ligne suivante :
FHandle: THandle;
Dans l'événement OnCreate de la fenêtre, ajoutez le code suivant :
FHandle:= CreateMailslot('\\.\mailslot\ExempleSimple', 0,
MAILSLOT_WAIT_FOREVER, nil);
Dans l'événement OnDestroy de la fenêtre, ajoutez le code suivant :
CloseHandle(FHandle);
Dans l'événement OnClick du bouton, ajoutez le code suivant :
var
lpNextSize, lpMessageCount, lpNumberOfBytesRead: Cardinal;
buffer: string;
begin
if GetMailslotInfo(FHandle, nil, lpNextSize, @lpMessagecount, nil) and
(lpMessageCount > 0) then
begin
SetLength(buffer, lpNextSize);
if ReadFile(FHandle, buffer[1], lpNextSize, lpNumberOfBytesRead, nil) then
mmMessages.Lines.Add(buffer);
end;
end;
Vous devriez avoir ceci dans l'éditeur :
unit ServeurFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
mmMessages: TMemo;
btnLire: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnLireClick(Sender: TObject);
private
FHandle: THandle;
public
end;
var
Form1: TForm1;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
FHandle:= CreateMailslot('\\.\mailslot\ExempleSimple', 0,
MAILSLOT_WAIT_FOREVER, nil);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
CloseHandle(FHandle);
end;
procedure TForm1.btnLireClick(Sender: TObject);
var
lpNextSize, lpMessageCount, lpNumberOfBytesRead: Cardinal;
buffer: string;
begin
if GetMailslotInfo(FHandle, nil, lpNextSize, @lpMessagecount, nil) and
(lpMessageCount > 0) then
begin
SetLength(buffer, lpNextSize);
if ReadFile(FHandle, buffer[1], lpNextSize, lpNumberOfBytesRead, nil) then
mmMessages.Lines.Add(buffer);
end;
end;
end.
Compilez le tout. Notre serveur est terminé !
Comment fonctionne-t-il ?
-
A la création de la fenêtre, nous créons un mailslot dont nous conservons le handle.
-
Lors d'un appui sur le bouton, nous regardons si des messages sont disponibles et si c'est le cas,
nous lisons un message et le copions dans le TMemo.
-
A la destruction de la fenêtre, nous libérons le handle du mailslot, ce qui a pour effet de fermer le
mailslot et de le supprimer (ainsi que les messages qu'il pourrait contenir).
4.1.2. Le client
Créez un nouveau projet et enregistrez-le sous le nom de 'Client'.
Enregistrez la fenêtre sous le nom de 'ClientFrm'.
Ajoutez un composant TMemo sur la fenêtre et appelez-le 'mmMessage'.
Ajoutez un composant Tbutton sur la fenêtre et appelez-le 'btnEcrire'.
Dans l'événement OnClick du bouton, ajoutez le code suivant :
var
FHandle: THandle;
buffer: PChar;
lpNumberOfBytesWritten: Cardinal;
begin
FHandle:= CreateFile('\\.\mailslot\ExempleSimple', GENERIC_WRITE,
FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if FHandle = INVALID_HANDLE_VALUE then
raise Exception.Create(SysErrorMessage(GetLastError))
else
begin
buffer:= PChar(mmMessage.Lines.Text);
WriteFile(FHandle, buffer^, Length(buffer),
lpNumberOfBytesWritten, nil);
CloseHandle(FHandle);
end;
end;
Compilez le tout. C'est fini pour notre client dont voici le fonctionnement :
-
Comme vous pouvez le constater, nous commençons par tenter d'ouvrir le mailslot. Si ce
n'est pas possible, la valeur du handle contient la valeur de la constante INVALID_HANDLE_VALUE.
Dans ce cas, nous affichons la raison de l'erreur en utilisant les fonctions GetlastError
(qui retourne le numéro de la dernière erreur) et SysErrorMessage qui affiche le message
correspondant à l'erreur.
-
Si tout c'est bien passé, nous écrivons le contenu du TMemo dans le mailslot, en passant par une
variable temporaire pour éviter un message d'erreur du compilateur en raison de la forme de la
déclaration de la fonction. Nous terminons en fermant le mailslot par un appel à CloseHandle.
Cela peut vous paraître complexe si vous n'êtes pas habitué à l'utilisation des fonctions de
l'API Windows. Mais en réalité c'est très simple. Surtout que vous utiliserez toujours cette forme d'appel.
Pour tester vos programmes, lancez le serveur en premier (pour qu'il crée le mailslot) et exécutez
ensuite le client. Dans le client, tapez un message et cliquez sur le bouton afin de placer le texte
dans le mailslot. Cliquez maintenant sur le bouton du serveur pour lire le message.
4.2. Allons plus loin
Si le principe de fonctionnement des mailslots vous plait, vous voudrez peut-être disposer de classes
Delphi vous permettant de les manipuler plus aisément afin d'éviter les appels direct à l'API Windows.
Je vous propose donc deux classes (TMailslotServer et TMailslotClient) simples mais qui devraient
faire l'affaire.
Dans la mesure où un certain nombre de fonctionnalités sont communes entre les serveur et le client,
j'ai dérivé ces deux classes d'une classe ancêtre (TMailslotCommon) implémentant ces fonctionnalités
partagées. Ces classes sont décrites dans une unité appelée mailslots.pas dont voici la partie interface:
unit Mailslots;
interface
uses
Classes, Windows;
type
TMailslotCommon = class
private
FMailslotName: string;
procedure SetMailslotName(const Value: string);
function GetConnected: boolean;
procedure SetConnected(const Value: boolean);
procedure SetLocation(const Value: string);
protected
FMSHandle: THandle;
FLocation: string;
procedure RaiseError(Msg: string = '');
property Location: string read FLocation write SetLocation;
public
constructor Create; virtual;
destructor Destroy; override;
function Connect: boolean; virtual; abstract;
procedure Disconnect; virtual;
function GetMailslotAddress: string;
property Connected: boolean read GetConnected write SetConnected;
property MailslotName: string read FMailslotName write SetMailslotName;
end;
TMailslotServer = class(TMailslotCommon)
public
function Connect: boolean; override;
function MessageCount: Cardinal;
function NextMessageSize: Cardinal;
procedure ReadBuffer(var Buffer; BufferSize: Cardinal);
procedure ReadStream(AStream: TStream);
function ReadString: string;
end;
TMailslotClient = class(TMailslotCommon)
public
function Connect: boolean; override;
procedure WriteBuffer(const Buffer; BufferSize: Cardinal);
procedure WriteStream(AStream: TStream);
procedure WriteString(AString: string);
property Location;
end;
Quelques précisions sur les membres de ces classes :
-
Le client propose les propriétés Location et MailslotName qui correspondent aux parties
variables de l'adresse du mailslot. L'adresse complète peut être connue en consultant
la propriété MailslotAddress qui en assemble les différentes parties :
\\propriété Location\mailslot\propriété MailslotName
-
Le serveur ne propose pas de propriété Location car cela n'a pas de sens puisque nous avons
vu qu'un processus ne peut créer que des mailslots locaux.
-
Les méthodes Connect et Disconnect devront être appelées pour créer/ouvrir le mailslot et pour
le refermer. La propriété Connected permet de savoir si le mailslot est ouvert.
-
Les méthodes Writexxx et Readxxx permettent d'utiliser le mailslot avec un buffer,
un flux de données (TStream) ou une simple chaîne de caractères.
5. Liens
| Articles | |
Delphi 2005 : Découvrez le futur Delphi 2005
| |
DirectX : Introduction à DirectX 9 en Delphi
| |
Variables d'environnement : Présentation, description et utilisation des variables d'environnement sous Windows
| |
Mailslots : Présentation des mailslots et de leur utilisation en Delphi pour la communication inter-processus
| | Projets complets avec sources | |
NumericalParser : Parser numérique en Delphi afin de transformer une chaîne de caractères en
valeur flottante ou entière.
| |
RegSearch : Composant de recherche dans la base de registre
| |
CDAReader : Lecture des informations contenues dans les fichier CDA de Windows
| |
ScreenSaverPreview : Composant d'affichage de l'aperçu des économiseurs d'écran de Windows
| |
ScanResources : Programme d'exploration des ressources des programmes ou des dll d'un répertoire
| |
ClipboardViewer : Démonstration de la détection des modifications et de l'affichage du contenu du presse-papier
| |
Matrix : Tentative de reproduction en Delphi de l'animation bien connue du film Matrix
| | Sources et exemples | |
EMFTransform : Transformation (rotation, inversion, miroir) d'un metafile Windows en mémoire
| |
DeleteKeyTree : Suppression récursive d'un clé de la base de registre
| |
MultiStrings : Routines de gestion de tableaux de chaînes C
| |
GetDllFilename : Pour récupérer le chemin d'une dll par son handle
| |
Extension du shell : Exemple d'extension du menu contextuel du shell de Windows
| |
TriStringGrid : Exemple de tri par colonne d'un composant TStringGrid à l'aide d'un algorithme
de tri rapide (quick sort)
| |
XPManifestCPL : Utilisation des contrôles XP dans une application du panneau de configuration (cpl)
| |
Bouboules : Modélisation à l'aide du design pattern Observer
| | Divers | |
Diagramme ternaire : Un logiciel gratuit de tracé de diagramme ternaire
|
|
|