Obligement - L'Amiga au maximum

Dimanche 21 juillet 2019 - 19:43  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles en d'autres langues


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Logiciels
 · Jeux
 · Scène démo
 · Divers


Jeux Amiga

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


Trucs et astuces

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


Glossaire

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


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Contact

David Brunet

Courriel

 


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 standards "intuition/intuition.h" et "intuition/intuition.i" :

C

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" :

C

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 :

C

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.


[Retour en haut] / [Retour aux articles] [Article précédent]