Suivez-nous sur X

|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : C - Communication intertâche (exemples de programmes)
(Article écrit par Max et extrait d'Amiga News Tech - septembre 1991)
|
|
Maintenant que nous avons vu les principes de base des messages d'Exec,
nous allons mettre tout cela en pratique, en construisant deux programmes distincts comprenant un protocole
de communication qui leur sera propre.
Bien entendu, dans un tel cas, les deux programmes doivent connaître ledit protocole. Un message étant avant tout
destiné à véhiculer des informations, il faut que l'envoyeur aussi bien que le destinataire sache de quelle
manière ces informations sont organisées. L'exemple le plus frappant est, encore une fois, Intuition : lorsqu'une
application reçoit un message en rapport avec sa fenêtre, Intuition remplit d'une manière précise et arrêtée un
IntuiMessage, qui décrit le type de l'évènement survenu (menu, souris, clavier...), donne la position de la souris,
l'heure système, l'état des touches spéciales du clavier (Ctrl, Alt, Shift et Amiga), etc. En fait, lorsque
l'on y regarde d'un peu plus près, l'on trouve la définition exacte de l'IntuiMessage dans les fichiers includes
standard "intuition/intuition.h" et "intuition/intuition.i" :
struct IntuiMessage
{ struct Message ExecMessage;
ULONG Class;
USHORT Code;
USHORT Qualifier;
APTR IAddress;
SHORT MouseX, MouseY;
ULONG Seconds, Micros;
struct Window *IDCMP Window;
struct IntuiMessage *SpecialLink;
};
|
Comme on peut le constater, un IntuiMessage commence par un message Exec standard, ceci afin que les fonctions
dédiées d'exec.library puissent le gérer sans problème (voir plus loin). Si nous définissons notre propre type
de message, il faudra absolument respecter ce schéma. Les autres champs de cette structure ne nous intéressent
pas ici et ne seront pas donc décris.
Types de messages
Un message est lui aussi décrit par une structure C, que l'on trouve dans "exec/ports.h" et "exec/ports.i" :
struct Message
{ struct Node mn_Node;
struct MsgPort *mn_ReplyPort;
UWORD mn_Lenght;
};
|
On retrouve en premier lieu une structure Node classique (Cf. mois dernier)
destinée à lier les messages entre eux (la tête de la liste étant le champ mp_MsgList de la structure MsgPort,
vue elle aussi le mois dernier). Suit un pointeur sur le port de messages de l'expéditeur, pour qu'Exec sache
qui l'avait envoyé lorsque le destinataire y répondra, et la taille des données suivant cet en-tête, exprimée
sur 16 bits non signés. Ce qui, lorsque l'on compte bien, peut désigner des données allant jusqu'à 65 536
octets, soit 64 ko. Une telle proportion sera bien entendue rarement atteinte, un message étant typiquement
composé de pointeurs sur des zones mémoires précises. M'enfin, encore une fois, chacun en fait ce qui lui plaît.
Le type du noeud (champ ln_Type de la structure Node) est NT_MESSAGE pour un message venant tout juste
d'être posté et NT_REPLYMSG pour un message auquel le destinataire vient de répondre. Cette distinction
permet à Exec de ne pas se mélanger les pinceaux lors du tri des noeuds. A noter qu'un message envoyé est
inséré dans la liste des messages du port destinataire avec la fonction Enqueue(), c'est-à-dire de manière
automatiquement triée par ordre décroissant des priorités (le message à la plus haute priorité passe en premier).
Cette particularité peut parfois permettre des applications intéressantes. Notez enfin qu'Exec se charge tout
seul comme un grand de modifier le type du message (NT_MESSAGE à NT_REPLYMSG) lorsque le besoin s'en fait
sentir ; cela est totalement transparent pour les applications concernées.
Communiquons
La bibliothèque Exec offre quatre fonctions dédiées à la gestion des messages. Ce sont PutMsg() pour
l'envoi, GetMsg() pour la réception, ReplyMsg() pour la réponse et WaitPort() pour l'attente. On peut également
ajouter Wait(), déjà entrevue le mois dernier alors que WaitPort() attend qu'un message (en fait, un signal)
arrive sur un port particulier, Wait() permet d'attendre sur plusieurs ports à la fois, donc plusieurs messages
de plusieurs sources différentes.
Une tâche désirant envoyer un message à une autre utilisera donc PutMsg(), ce qui implique évidemment
qu'elle connaisse l'adresse du port de réception. Elle devra par la suite attendre sur son propre port
que le message lui soit retourné par le destinataire, à la suite de quoi le message pourra être libéré -
s'il avait été alloué avec AllocMem() - ou réutilisé le cas échéant. Quoiqu'il arrive, l'expéditeur ne
doit pas modifier le contenu du message avant que le destinataire n'y ait effectivement répondu (rappel :
les messages sont transmis par adresse, et non par copie dans une mémoire tampon).
Démonstration
Les deux programmes ci-dessous communiquent entre eux au moyen d'un message défini par nos soins. Le premier,
transmet tous les caractères tapés au clavier au second, qui les affiche alors dans sa fenêtre. Le contrôleur
teste également si l'un des caractères reçus est ESC (ASCII 27), auquel cas il envoie un message à l'afficheur
pour lui signaler qu'il est temps de quitter. Cet exemple n'est pas certes très utile tel quel, mais il démontre
bien les mécanismes de la communication intertâche.
Le message utilisé est défini dans le fichier d'en-tête "Message.h" commun aux deux programmes, par la structure que
voici :
struct MaxMessage
{ struct Message msg; /* Message Exec standard */
ULONG type; /* type du message */
ULONG code; /* code de la touche appuyée */
|
Comment les deux programmes s'y prennent-ils pour savoir si l'autre est chargé ?
C'est tout bête : chaque programme recherche si le MsgPort de l'autre est déjà créé. Si ce n'est pas le cas,
il attend patiemment avec Wait() que l'autre programme se charge et lui signale son arrivée. Si au
contraire le MsgPort cherché existe, cela veut dire que l'autre programme est déjà en train d'attendre,
auquel cas on lui signale simplement que l'on vient d'arriver. Plus simplement, le premier arrivé attend l'autre.
Pour le reste, les listings sont suffisamment commentés pour être facilement compréhensibles de tout un chacun.
Listing 1
/*
* Message.h
*
* Fichier entête commun à Controleur.c et Afficheur.c
*
* ©1991, par Max pour ANT
*/
#include <exec/types.h>
#include <exec/ports.h>
/* Noms des MsgPorts du Contrôleur et de l'Afficheur */
#define controlName "Controleur.Port"
#define afficheName "Afficheur.Port"
/* Petits raccourcis pratiques */
#define controlTask (controlPort->mp_SigTask)
#define controlMask (1L<<controlPort->mp_SigBit)
#define afficheMask (1L<<affichePort->mp_SigBit)
#define afficheTask (affichePort->mp_SigTask)
/* structure MaxMessage */
struct MaxMessage
{ struct Message msg; /* Message Exec standard */
ULONG type; /* Type du message (voir ci-dessous) */
ULONG code; /* code de la touche appuyée */
};
/* types de MaxMessages possibles */
#define TOUCHE 1L /* touche appuyée */
#define FIN 2L /* mettre fin au programme */
#define MAXMSGSIZE (sizeof(struct MaxMessage)-sizeof(struct Message))
|
Listing 2
* Controlleur.c
*
* Envoie, par un message particulier, des données à Afficheur.c
*
* ©1991, Max pour ANT
*
* SAS/Lattice C 5.10:
* Compilation:
* lc -cist iv -y Controleur
*
* Linkage:
* blink FROM LIB:c.o,Controleur.o
* TO Controleur
* LIBRARY LIB:amiga.lib,LIB:lc.lib
* SMALLCODE
* SMALLDATA
* NODEBUG
* DEFINE __main=__tinymain
*/
#include <exec/types.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <devices/console.h>
#include <stdio.h>
#include <stdlib.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/console.h>
#include "message.h"
/* Données globales */
struct IOStdReq consoleIO;
struct NewWindow nw =
{ 0, 0, 200, 10,
0, 1,
RAWKEY,
WINDOWDRAG|SMART_REFRESH|NOCAREREFRESH|RMBTRAP,
NULL, NULL, "Controlleur", NULL, NULL,
0, 0, 0, 0, WBENCHSCREEN
};
struct MaxMessage max =
{ { { NULL, NULL, NT_MESSAGE, 0, NULL }, NULL, MAXMSGSIZE },
0, 0
};
/* Variables globales */
struct Library *IntuitionBase = NULL;
struct MsgPort *controlPort = NULL, *affichePort = NULL;
struct Window *controlWin = NULL;
struct ConsoleDevice *ConsoleDevice = NULL;
struct MaxMessage *maxmsg = NULL;
/* Prototypes des fonctions */
VOID main(VOID);
VOID cleanexit(LONG code);
/******************************************************************
* C'est parti ! *
*****************************************************************/
VOID main(VOID)
{
struct MsgPort *winport;
struct IntuiMessage *im;
struct InputEvent ievent = { NULL,IECLASS_RAWKEY,0,0,0,NULL,NULL };
UBYTE buf[4];
BOOL fini = FALSE;
/* On ne se lance pas deux fois ! */
if (FindPort(controlName))
cleanexit(RETURN_WARN);
/* Ouvre l'intuition.library */
if (!(IntuitionBase = OpenLibrary("intuition.library", 33L)))
cleanexit(RETURN_FAIL);
/* Ouvre la fenêtre */
if (!(controlWin = OpenWindow(&nw)))
cleanexit(RETURN_FAIL);
/* Prend le pointeur sur le MsgPort de la fenêtre */
winport = controlWin->UserPort;
/* Crée un MsgPort pour communiquer avec l'afficheur */
if (!(controlPort = CreatePort(controlName, NULL)))
cleanexit(RETURN_FAIL);
/* Initialise le ReplyPort du message */
max.msg.mn_ReplyPort = controlPort;
/* Ouvre le console.device */
if (OpenDevice("console.device", -1, &consoleIO, 0))
cleanexit(RETURN_FAIL);
/* Prend l'adresse de base du console.device */
/* nécessaire pour RawKeyConvert() */
ConsoleDevice = (struct ConsoleDevice *)consoleIO.io_Device;
/* Recherche (et attend au besoin) l'afficheur */
affichePort = FindPort(afficheName);
if (!affichePort)
{ Wait(controlMask);
affichePort = FindPort(afficheName);
}
else
/* Signale à l'afficheur qu'on est arrivé */
Signal(afficheTask, afficheMask);
/* Active notre fenêtre pour recevoir les IDCMP */
ActivateWindow(controlWin);
/* Boucle principale */
while (!fini)
{ WaitPort(winport);
while ((im = (struct IntuiMessage *)GetMsg(winport)))
{ if (im->Class == RAWKEY)
{ if (!(im->Code & IECODE_UP_PREFIX))
{ ievent.ie_Code =im->Code;
ievent.ie_Qualifier =im->Qualifier;
ievent.ie_EventAddress=*((APTR*)im->IAddress);
if (RawKeyConvert(&ievent, buf, 2, NULL) == 1)
{ if (*buf == 27)
{ fini = TRUE;
max.type = FIN;
max.code = '\0';
}
else
{ max.type = TOUCHE;
max.code = (ULONG)*buf;
}
/* Envoie le message à l'afficheur... */
PutMsg(affichePort,(struct Message *)&max);
/* attend sa réponse... */
WaitPort(controlPort);
/* et l'enlève de notre MsgPort */
GetMsg(controlPort);
/* On peut maintenant réutiliser
le message comme on veut */
}
}
}
ReplyMsg((struct Message *)im);
}
}
cleanexit(RETURN_OK);
}
/******************************************************************
* Referme et libère tout ce qui a été ouvert et alloué et sort *
*****************************************************************/
VOID cleanexit(LONG code)
{
if (ConsoleDevice) CloseDevice(&consoleIO);
if (controlPort) DeletePort(controlPort);
if (controlWin) CloseWindow(controlWin);
if (IntuitionBase) CloseLibrary(IntuitionBase);
exit(code);
}
|
Listing 3
* Afficheur.c
*
* Affiche les données qui lui sont envoyées par Controleur.c
*
* ©1991, Max pour ANT
*
* SAS/Lattice C 5.10
* Compilation :
* lc -cist -v -yAfficheur
*
* Linkage :
* blink FROM LIB:c.o,Afficheur.o
* TO Afficheur
* LIBRARY LIB:amiga.lib,LIB:lc.lib
* SMALLCODE
* SMALLDATA
* NODEBUG
* DEFINE _main=__tinymain
*/
#include <exec/types.h>
#include <exec/ports.h>
#include <intuition/intuition.h>
#include <graphics/rastport.h>
#include <libraries/dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/dos.h>
#include "Message.h"
/* Fenêtre d'affichage */
struct NewWindow nw =
{ 0, 11, 640, 30,
0, 1,
NULL, /* aucun IDCMP */
WINDOWDRAG|SMART_REFRESH|NOCAREREFRESH,
NULL, NULL, "Afficheur", NULL, NULL,
0, 0, 0, 0, WBENCHSCREEN
};
/* Variables globales */
struct Library *IntuitionBase = NULL, *GfxBase = NULL;
struct MsgPort *controlPort = NULL, *affichePort = NULL;
struct Window *afficheWin = NULL;
struct MaxMessage *maxmsg = NULL;
/* Prototypes des fonctions */
VOID main(VOID);
VOID cleanexit(LONG code);
/******************************************************************
* C'est parti ! *
*****************************************************************/
VOID main(VOID)
{
UBYTE *buf = "Touche : -"; /* 10 caractères */
BOOL fini = FALSE;
struct RastPort *rp;
/* On ne se lance pas deux fois ! */
if (FindPort(afficheName))
cleanexit(RETURN_WARN);
/* Ouvre l'intuition.library */
if (!(IntuitionBase = OpenLibrary("intuition.library", 33L)))
cleanexit(RETURN_FAIL);
/* Ouvre la graphics.library */
if (!(GfxBase = OpenLibrary("graphics.library", 33L)))
cleanexit(RETURN_FAIL);
/* Ouvre la fenêtre */
if (!(afficheWin = OpenWindow(&nw)))
cleanexit(RETURN_FAIL);
/* Prend le pointeur sur le RastPort de la fenêtre */
rp = afficheWin->RPort;
/* Crée un MsgPort pour communiquer avec l'afficheur */
if (!(affichePort = CreatePort(afficheName, NULL)))
cleanexit(RETURN_FAIL);
/* Recherche (et attend au besoin) le contrôleur */
controlPort = FindPort(controlName);
if (!controlPort)
{ Wait(afficheMask); /* attend l'arrivée du contrôleur */
controlPort = FindPort(controlName);
}
else
/* Signale au contrôleur qu'on est arrivé */
Signal(controlTask, controlMask);
/* Boucle principale */
while (!fini)
{ WaitPort(affichePort);
while ((maxmsg = (struct MaxMessage *)GetMsg(affichePort)))
{ switch(maxmsg->type)
{ case FIN:
fini = TRUE;
sprintf(buf, "%-10s", "fini !");
break;
case TOUCHE:
buf[9] = (UBYTE)maxmsg->code;
break;
default:
break;
}
/* Répond au message (le contrôleur attend) */
ReplyMsg((struct Message *)maxmsg);
/* Affiche le contenu du buffer */
Move(rp, 5, 20);
Text(rp, buf, 10);
}
}
Delay(2 * TICKS_PER_SECOND); /* petit délai */
cleanexit(RETURN_OK); /* avant de partir */
}
/******************************************************************
* Libère et ferme tout ce qui a été alloué et ouvert *
*****************************************************************/
VOID cleanexit(LONG code)
{
if (affichePort) DeletePort(affichePort);
if (afficheWin) CloseWindow(afficheWin);
if (GfxBase) CloseLibrary(GfxBase);
if (IntuitionBase) CloseLibrary(IntuitionBase);
exit(code);
}
|
Dernière minute
Un détail que j'ai oublié de préciser dans l'article : comment les deux programmes s'y prennent-ils pour
savoir si l'autre est chargé ?
C'est tout bête : chaque programme recherche si le MsgPort de
l'autre est déjà créé. Si ce n'est pas le cas, il attend patiemment
avec Wait() que l'autre programme se charge et lui signale son arrivée. Si au contraire le
MsgPort cherché existe, cela veut dire que l'autre programme est déjà en train
d'attendre, auquel cas on lui signale simplement que l'on vient d'arriver.
Plus simplement, le premier arrivé attend l'autre.
Mise à jour de décembre 2019 : merci à Yann-Gaël Guéhéneuc qui vous propose une
modification du code pour qu'il compile avec l'Amiga SDK 3.9 et VBCC.
|