Obligement - L'Amiga au maximum

Vendredi 17 novembre 2017 - 22:17  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · 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 in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


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 - gestion des messages
(Article écrit par Laurent Faillie et extrait d'Amiga News - janvier 1999)


Avec l'article de ce mois-ci, on termine la partie la plus pénible de cette série.

En effet, après avoir parlé de la gestion des messages dans cet article, nous aurons toutes les bases nécessaires pour attaquer le développement de vraies applications.

Les messages

Bon, les messages. D'abord, il s'agit de démystifier un peu la chose. Contrairement à ce que beaucoup de débutants pensent, surtout s'ils développent aussi sous m$-Windows, ça n'a rien de vraiment compliqué. Eh oui, le système de "guillaume porte" use et abuse d'un mécanisme similaire, mais avec bien évidemment beaucoup plus de lourdeur. Mais revenons à notre Amiga préféré...

Prenons le listing suivant :

(le listing de cet article n'a pas été retrouvé)

Au début, on trouve les habituelles #includes. Je vous laisse les décortiquer vous-même : vous vous y reconnaîtrez facilement, car nous les avions déjà vues. Il n'y a rien de nouveau depuis le dernier article.

On trouve juste derrière les initialisations. On oublie les définitions des gadgets pour le moment, ce n'est pas le sujet de cet article et on y consacrera un autre : il y a tellement à dire ! Pour le moment, utilisez ces définitions telles quelles...

En ce qui concerne la déclaration des fenêtres, depuis le mois dernier, nous sommes maintenant en terrain connu. Remarquez simplement que nous avons renseigné le fameux "IDCMPFlags". C'est dans ce champ que nous devons indiquer au système les événements que nous attendons.

Dans notre cas, il n'y en a que deux :
  • CLOSEWINDOW : prévenir lorsque l'utilisateur clique sur le gadget de fermeture de la fenêtre.
  • GADGETUP : prévenir lorsque l'utilisateur a cliqué sur un de nos boutons.
Il existe aussi un GADGETDOWN, qui a presque la même fonction. Dans le cas du GADGETDOWN, il faut que le gadget ait le drapeau GADGIMMEDIATE, nous recevrons un message dès que l'utilisateur appuiera sur le bouton. Dans notre cas, avec GADGETUP pour la fenêtre et RELVERIFY pour le gadget, le message nous parvient lorsque l'utilisateur relâche le bouton.

Dans notre exemple, nous n'utilisons que des boutons de type booléen (ils sont relâchés dès que l'utilisateur arrête d'appuyer dessus), donc une seule méthode est nécessaire. On utilise les deux simultanément lorsque l'on doit gérer des boutons de type "toggle" qui nécessite un deuxième clic pour être relâché.

On recevra donc un GADGETDOWN lors du premier clic et un GADGETUP lors du second.

Il existe encore bien d'autres drapeaux que nous aurions pu utiliser :
  • REQVERIFY, REQSET et REQCLEAR : une requête doit s'afficher, s'est affichée ou s'est refermée.
  • SIZEVERIFY et NEWSIZE : changement de la taille de la fenêtre.
  • REFRESHWINDOW : Intuition nous demande de rafraîchir la fenêtre, (pour les fenêtres avec rafraîchissement SMART et SIMPLE).
  • MOUSEBUTTONS : envoie un message si l'utilisateur clique sur la souris. Attention, ce message n'est envoyé que si le clic se passe ailleurs que sur un bouton, sinon Intuition génère l'événement associé au bouton.
  • MOUSEMOVE et DELTAMOVE : Intuition nous enverra des messages qui nous permettront de suivre les mouvements de la souris. Dans le premier cas, on obtiendra la position absolue du curseur ; alors que dans le second, c'est la différence avec la dernière position transmise.
  • MENUVERIFY et MENUPICK : affichage ou sélection d'un (ou plusieurs) menu(s).
  • NEWPREFS : changement des préférences. Attention : cette méthode ne fonctionne qu'avec les préférences d'AmigaOS 1.x. A partir du 2.0, il vaut mieux utiliser une notification sur le répertoire contenant les variables d'environnement.
  • DISKINSERTED et DISKREMOVED : insertion ou éjection d'un disque.
  • ACTIVEWINDOW et INACTIVEWINDOW : la fenêtre devient active ou inactive.
  • VANILLAKEY et RAWKEY : frappe sur une touche du clavier. Avec le premier, on obtient le code ASCII de la touche, avec le second, c'est son code "brut" (d'où le nom), c'est-à-dire le code généré par le clavier avant qu'il ne soit traité par le "KeyMap". Si les deux drapeaux sont présents : un VANILLAKEY est envoyé si le code ASCII ne contient qu'un seul octet, contre un RAWKEY dans le cas contraire (les touches renvoyant plusieurs octets sont les touches de fonction et de mouvements de curseur).
  • INTUITICKS : on demande qu'Intuition nous envoie régulièrement des messages (tous les 50e de seconde).
Ce peut être intéressant pour faire bouger une animation par exemple, mais il ne faut pas se baser sur la régularité des messages : elle dépend principalement de la charge du système. Nous verrons qu'il existe des méthodes beaucoup plus précises et plus souples grâce au timer.device.

Les drapeaux suivants sont apparus avec le Kickstart 2.0 (version == 36) et sont donc ignorés par les systèmes 1.x :
  • IDCMP_MENUHELP : l'utilisateur a appuyé sur la touche [HELP] pour avoir de l'aide sur un menu.
  • IDCMP_CHANGEWINDOW : la fenêtre a changé, ce peut être un redimensionnement, mais aussi un changement d'état ZOOM (la fenêtre est iconifiée ou est désiconifiée).
  • IDCMP_IDCMPUPDATE : message de notification de la part d'un gadget BOOPSI, on y reviendra...
Et celui-ci avec le 3.0 (version == 39) :
  • IDCMP_GADGETHELP : idem que IDCMP_MENUHELP mais pour les gadgets, l'utilisateur a appuyé sur [HELP] alors que le curseur était sur un bouton.
Enfin, ces derniers drapeaux sont exclusivement utilisés par le système : LONELYMESSAGE et WBENCHMESSAGE.

Avant de rentrer plus en détail dans le sujet, juste un petit aparté sur les ????VERIFY. Ces messages nous arrivent lorsque quelque chose doit altérer notre fenêtre (par exemple, l'ouverture d'une requête ou d'un menu). Intuition attendra que nous répondions à ce message avant de continuer.

C'est utile, par exemple, si l'on doit sauvegarder le contenu de notre fenêtre.

Il faut éviter qu'un message ????VERIFY reste en attente pendant un long moment. Proscrire aussi les appels au DOS. Par exemple, nous sommes en train de traiter un REQVERIFY (mais ce serait pareil avec un SIZEVERIFY ou un MENUVERIFY) et nous voulons ouvrir un fichier.

Malheureusement, il se trouve sur un volume qui n'est pas encore "monté". Le système devrait donc nous afficher une requête "Veuillez insérer le volume ???? dans un lecteur". Oui, mais voilà, il ne peut afficher quoi que ce soit tant que nous n'avons pas répondu au message... DeadLock ! On attend la réponse à une requête qui ne peut s'afficher et qui ne viendra donc... jamais !

Problème de notation

L'exemple qui accompagne cet article doit pouvoir se compiler sur n'importe quel système. La notation utilisée est donc celle du 1.3. Avec le 2.0, Commodore avait décidé de modifier les noms des objets définis dans ses includes ; ceci afin d'éviter l'utilisation de noms génériques qui pouvaient être écrasés par d'autres noms de l'application. C'est ainsi que le nom des drapeaux d'activation des boutons gagne le préfixe GACT_ (donc RELVERIFY devient GACT_RELVERIFY).

On notera le cas particulier de GADGIMMEDIATE (transformé en GACT_IMMEDIATE) alors que les drapeaux IDCMP ont pour préfixe... IDCMP_ (GADGETDOWN devient IDCMP_GADGETDOWN).

A noter que, comme d'habitude, les ingénieurs d'Amiga ont bien fait les choses : les includes utilisent automatiquement des fichiers de compatibilité, ce qui fait qu'un source prévu pour le 1.3 se compilera sans problème sous 2.0 ou plus ! Y en a qui devraient en prendre de la graine, suivez mon regard ! Bon, revenons au code !

Tout au début de la fonction main(), on trouve l'habituel "atexit()" qui nous assure que la fonction "menage()" sera appelée à la fin de l'exécution de notre programme et donc que tout ce que nous avons ouvert sera refermé.

On commence bien évidemment par ouvrir les bibliothèques, puis les fenêtres.

Vient ensuite la boucle principale du programme, on y reste tant que l'une au moins des fenêtres reste ouverte.

Maintenant, nous arrivons à la partie la plus intéressante : la gestion des messages. La méthode bourrin serait de faire une boucle sans fin : si un message est en attente, on le traite, sinon on boucle... C'est ce qui fut employé avec l'infâme AmigaBasic bien connu des utilisateurs de machines sous Workbench 1.x : tout le processeur est utilisé, que notre programme travaille ou non.

C'est propre n'est-ce pas ? Si tout le monde faisait comme ça, il faudrait au moins un Pentium à 450 MHz pour faire tourner un simple traitement de texte (toute ressemblance avec un environnement plein de fenêtres existant ne serait que fortuite). Bon, nous ne sommes pas chez Microsoft (eh oui, c'est eux qui ont pondu cette daube ! Une cochonnerie de plus à leur actif), on va faire propre !

Si vous avez déjà utilisé un gestionnaire de tâche ("task manager"), vous aurez remarqué que les programmes lancés sur le système sont classés en trois catégories :
  • RUNNING : à un instant donné, un seul programme peut être dans cet état, c'est le programme qui est en train d'utiliser le processeur.
  • READY : il s'agit des programmes qui attendent que le processeur soit disponible.
    Le système découpe l'utilisation du processeur en petits instants qu'il distribue aux tâches READY. S'il y en a plusieurs qui attendent, il donnera la main à celle qui a la priorité la plus grande. Attention : contrairement à ce qui se passe sous Unix ou sous VMS, les priorités ne sont pas dynamiques, donc les tâches d'une priorité donnée n'auront jamais la main tant qu'une tâche de priorité supérieure aura besoin du processeur ! Eh oui, AmigaOS est un des systèmes demandant le moins de ressources mais ceci a quand même un prix... Certains d'entre vous connaissent sans doute des programmes tels qu'Executive (disponible sur Aminet) qui implémentent une gestion des tâches dynamique. Si plusieurs tâches de même priorité sont READY, le système leur donnera un petit temps processeur les unes après les autres.
  • WAITING : dans cette catégorie, on trouvera tous les autres programmes. Ils sont en attente d'événements.
La capture d'écran suivante provient d'ARTM qui est un des gestionnaire de tâche du domaine public et que l'on peut trouver sur Aminet.

ARTM

On y voit bien les trois types de catégories. On fera remarquer à ceux qui ne sont pas bien réveillés que le programme RUNNING est forcément ARTM lui-même. Eh oui, c'est forcément lui qui utilise le processeur puisqu'il est en train de capturer l'état du système !

Si un programme n'a rien à faire... eh bien, qu'il ne fasse rien ! Cette lapalissade est mise en pratique grâce à la fonction Wait().

Elle va placer notre programme en "waiting" jusqu'à l'arrivée d'un des signaux activés dans le masque passé en paramètre.

Pour recevoir nos messages, Intuition a créé automatiquement un port à chacune de nos fenêtres. A chaque port est associé un signal qui sera dressé lorsqu'un message arrive sur le port. Notez bien : le signal est mis lorsqu'un message arrive. C'est très important et on en reparlera tout à l'heure...

On devine tout de suite une limitation : le nombre de signaux disponibles pour une tâche est forcément limité. Ils sont stockés dans un entier long donc il n'y en que 32 de disponibles par tâche... mais 16 sont déjà alloués par le système.

Ces signaux sont aussi utilisés lorsque l'on communique avec une autre tâche et donc par extension à un device. 16 possibilités de connexion, c'est quand même un peu limité me direz-vous. Eh bien non !

Petite astuce : il est aussi possible de ne pas créer de port de message avec la fenêtre. Vous trouverez la méthode à suivre dans les autodocs, mais voici, en résumé, comment procéder : il faut d'abord créer la fenêtre sans drapeau IDCMP, ainsi aucun port n'est créé avec la fenêtre.

Ensuite, vous devez associer manuellement un port à la fenêtre et activer les drapeaux des événements attendus par un ModifyIDCMP() (voir l'autodoc de cette fonction pour plus de détails). Lors de la fermeture de la fenêtre, on commence par remettre le window.UserPort à NULL, puis on ferme la fenêtre par un CloseWindow().

En regroupant ainsi le port, on peut utiliser autant de fenêtres que l'on veut ; enfin, tant qu'il y a de mémoire disponible.

Wait() prend comme paramètres les signaux que nous attendons et renvoie le ou les signaux activés. Notons au passage qu'elle remet ces signaux à zéro.

Grâce au "sig = SIGBREAKB_CTRL_C", nous indiquons que nous souhaitons être avertis d'un Contrôle-C. Nous le testons par la ligne :

if(sig & SIGBREAKB_CTRL_C)

Attention : certains compilateurs tel que le SAS ont des mécanismes internes pour tester le CTRL_C. Il faudra donc les désactiver sinon notre programme sera stoppé avant même que nous n'apercevions ce signal. Pour GCC, c'est un peu le même problème, puisque là, l'ixemul.library intercepte ce signal pour simuler l'action qu'il y aurait sous Unix. Bref, quel que soit votre compilateur, je vous conseille de jeter un oeil sur sa documentation...

Les lignes...

if(win1) sig |= 1L << win1->UserPort->mp_SigBit;
if(win2) sig |= 1L << win2->UserPort->mp_SigBit;

...activent les bits correspondant à chacune des fenêtres... en ayant bien pris soin de vérifier qu'elle est ouverte. On remarque que le champ mp_SigBit du port contient le numéro du port utilisé, d'où le décalage à gauche.

Avant d'arriver sur le Wait(), sig a donc les bits actifs pour la valeur correspondant au Ctrl-C, ainsi que ceux des fenêtres ouvertes.

On trouve donc ensuite notre fameux Wait(), l'exécution s'arrête jusqu'à ce qu'un des signaux soit activé.

Suivent les boucles de lecture des messages. Nous aurions pu utiliser la même méthode qu'avec le break, c'est-à-dire tester si le signal correspondant à chaque fenêtre est activé, ce qui aurait donné :

        sig = Wait( sig );
        if(sig & (1L << win1->UserPort->mp_SigBit)){
                ... bla bla bla ...
        }
        if(sig & (1L << win2->UserPort->mp_SigBit)){
                ... bla bla bla ...
        }

(les petits futés remarqueront que j'ai simplifié volontairement les tests pour rendre ce code plus clair, mais... réveil du Guru garanti si vous l'utilisez tel quel !)

Ce code est simple mais ne fonctionnera pas. Eh oui, comme je vous l'ai dit, les signaux ne sont mis que lorsqu'un message arrive. Imaginons qu'un des traitements prenne plusieurs secondes. Pendant ce temps, l'utilisateur peut cliquer sur des boutons ou fermer une fenêtre. Lorsque le traitement est terminé, on revient au Wait(), qui ne stoppe rien du tout vu que des messages sont arrivés depuis le dernier passage (ceux qui ont été créés par l'utilisateur pendant le traitement). Le premier message est traité et retour sur le Wait(). Ce coup-ci, notre programme est stoppé !

Eh bien oui, le Wait() précédent a remis les signaux à zéro, et il faut attendre que des nouveaux messages arrivent avant que notre tâche revienne en READY, et ce même s'il y a d'autres messages dans les queues !

Ici, la solution correspond à créer une boucle do-while dans laquelle on reste tant qu'il y a des messages (utilisation du booléen "encore").

Reste maintenant à étudier ces messages : la fonction GetMsg() renvoie un pointeur sur le premier message en attente, ou NULL s'il n'y en a pas.

Regardons de plus près les champs contenus dans les messages. En premier, on trouve évidemment une structure message : les IntuiMessage sont des extensions des ExecMessage, seule structure que les fonctions de messages savent gérer. Lorsque nous aborderons ARexx, nous verrons que les messages échangés sont aussi des excroissances des ExecMessage.

Le champ "Class" contient le code IDCMP du message. Dans notre exemple, ce champ est traité par un switch/case classique.

Les champs "Code" et "Qualifier" dépendent du type de message. Pour un VANILLAKEY par exemple, "Code" contient le code ASCII de la touche activée alors que "Qualifier" contiendra les codes de "qualifiers" activés (si par exemple CTRL, ALT ou une touche Amiga est appuyée).

"IAddress" contient un pointeur qui dépend lui aussi de la classe du message.

Dans notre cas, il contient l'adresse du gadget qui a été cliqué.

"MouseX" et "MouseY" contiennent les coordonnées de la souris au moment de l'événement. Ils sont relatifs au coin supérieur gauche. Si le drapeau IDCMP_DELTAMOVE est mis, ces coordonnées sont relatives à la différence par rapport au dernier message envoyé à cette fenêtre.

"Seconds" et "Micros" sont des copies de l'horloge système au moment où le message a été envoyé.

Enfin, "IDCMPWindow" contient le pointeur vers notre fenêtre.

Voilà, comme vous le voyez, ces messages sont assez simples mais complets. Ils sont très faciles à utiliser.

Une fois que nous en avons fini avec eux, il faut le signifier au système pour qu'il les libère ou qu'il les utilise à d'autres fins. C'est fait grâce à la fonction ReplyMsg() qui prend en argument l'adresse du message.

Comme dit précédemment, ReplyMsg() conditionne le bon fonctionnement du système (par exemple, pour éviter un DeadLock pour les messages Verify????), et est obligatoire pour tous les messages que nous recevons !

Attention : il faut répondre le plus rapidement possible, pour éviter de monopoliser les ressources du système, mais parallèlement, il ne faut pas utiliser les données d'un message après y avoir répondu. En effet, répondre à un message indique au système qu'il peut en faire ce qu'il veut, donc les données qu'il contient peuvent (et seront sans doute) altérer si vous tentez d'y accéder après un ReplyMsg().

Une fois que toutes les fenêtres sont fermées, ou que l'utilisateur a fait un Ctrl-C, on sort de la boucle de traitement des messages et notre programme se termine... après être passé par menage() grâce au "atexit()" du début. Certains pourraient s'étonner que l'on ferme violemment les fenêtres par un CloseWindow(). Peut-être y a-t-il des messages en attente ? Il faudrait sans doute les effacer avant de fermer les fenêtres ? Pas la peine, les ingénieurs d'Amiga y ont pensé avant nous et le CloseWindow() libère automatiquement tous les messages qui restent dans les queues... Haaaa, qu'ils sont forts !

Voilà, j'espère que vous avez réussi à tenir jusqu'au bout de cet article. C'est vraiment le point le plus important de la programmation sur Amiga, car même si vous n'utilisez pas d'interface graphique pour vos programmes, vous utiliserez une méthode similaire dès que vous souhaiterez faire communiquer plusieurs tâches, et ceci inclut aussi les devices et donc les entrées/sorties...

C'en est fini des cours barbants ! La prochaine fois, on commencera vraiment à coder utile, en posant les bases d'un jeu !


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