Obligement - L'Amiga au maximum

Vendredi 19 avril 2024 - 18:50  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

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

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des 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,
ALL


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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : C - premiers pas
(Article écrit par Laurent Faillie et extrait d'Amiga News - octobre 1998)


C reparti !

C'est un dimanche pluvieux. Avachi devant votre écran de TV, qui ne fait que de couper vos pubs préférées par des séries débiles et ricaines, vous vous dites : "Tiens, si aujourd'hui je faisais un super programme méga-génial sur mon Amiga, avec tout plein de couleurs, de fenêtres et de gadgets et de...".

Mais non ! Ça vous revient maintenant : vous ne savez pas programmer l'Amiga. Horreur, malheur ! Non, car tel le Zorro moyen, me voici accourir pour vous apporter un peu de lumière dans ce monde de brutes, où Billou a fermé la fenêtre...

Attention : certains des listings de cet article peuvent planter. C'est pourquoi sauvegardez ce qui doit l'être avant de les exécuter ! Bon, c'est pour les besoins de l'initiation (elle a bon dos !) et uniquement dans cet article, les suivants ne poseront plus de problème... enfin normalement. ;-D

Bien choisir son destrier : le compilo

Le C a toujours été le langage de prédilection sur Amiga, c'est pourquoi un large choix de compilateurs s'offre à nous, qu'ils soient commerciaux ou dans le DP. Évidemment, chaque programmeur dira que le compilateur qu'il utilise est le meilleur, cependant, pour le néophyte qui n'est pas encore équipé, voici ceux que je préconise. Ce sont simplement ceux installés sur mes Amiga et dont je parlerai souvent dans mes articles.

La Maserati, son excellence GCC

Vous avez un Amiga accéléré (68020 ou plus), beaucoup de mémoire (au moins 8 Mo, 10 de préférence), de la place sur votre disque dur ou un lecteur de CD et vous savez ce qu'est un compilateur C : GCC est celui qu'il vous faut !

Pour le même prix, c'est-à-dire rien, il est gratuit, vous aurez droit au C, C++, Objective C, Ada et même Fortran. Conçu à l'origine pour Unix, vous le retrouverez comme compilateur natif des OS libres comme Linux, NetBSD, FreeBSD, etc. mais il existe aussi des portages sur des Unix commerciaux (HP-UX, AIX, Solaris, Digital Unix, etc. en fait, je ne crois pas qu'il existe un Unix récent sur lequel il n'ait pas été porté !). On le retrouve aussi sur Vax/VMS, BeOS, Mac, ST et même sur les Prehistorics Computers !

Outre le fait qu'il soit toujours plaisant d'utiliser le même compilateur d'une machine à l'autre, je lui trouve de nombreux avantages dont :
  • Affichage de nombreuses alertes ("warnings"), même très tordues, génial pour dénicher les bogues.
  • Suit très fidèlement la norme.
  • Peu de bogues...
Comme inconvénients on notera :
  • Une documentation limitée, voire impénétrable !
  • Comme c'est un portage d'Unix, adieu les GUI, bonjour les lignes de commande à rallonge.
  • Pour la même raison, il nécessite beaucoup de ressources, que ce soit en processeur comme en mémoire. Bon, je l'ai quand même installé sur mon vieil Amiga 1000 (68010 avec 10 Mo de mémoire). Ça passe, mais que ça rame...
Allez, première astuce : par défaut, il inclut les informations de débogage dans les exécutables qu'il génère, l'option "-s" ajoutée dans sa ligne de commande diminuera la taille de vos programmes de plusieurs dizaines de ko. De même, vous pouvez virer sans problème les segments "Debug" et "Symbols" de ses propres exécutables ce qui, en plus de libérer de la place sur le disque, accélérera les compilations, le chargement en mémoire...

La deudoche, DICE

Ce fut mon premier compilateur C sur Amiga. On préférera aller le chercher sur Aminet car la dernière archive en date contient la version professionnelle avec toutes les sources ! Elle comporte une GUI très bien faite et une interface ARexx complète.

Relisez mes articles dans les anciens Amiga News et vous verrez qu'on peut même l'utiliser sur un simple Amiga 68000 muni d'1 Mo de mémoire et sans disque dur !

Comme avantage supplémentaire, la documentation est très bien foutue et comporte une initiation pour débutant, accompagnée d'exemples pour la création d'une bibliothèque, d'un périphérique logique.

Il est aussi très bien intégré au système et gère pour vous l'ouverture automatique des bibliothèques, les arguments du Workbench... Bon, il contient aussi quelques bogues et l'exécutable n'est pas vraiment optimisé.

Mention spéciale pour le Lattice (ou SAS C)

Ce vieux compilateur, bien que plus vendu depuis un bail, continue à être soutenu par ses développeurs qui fournissent régulièrement des correctifs. Il possède un avantage indéniable : Commodore l'a utilisé pour développer le système, on trouve donc beaucoup d'exemples de programmation l'utilisant.

Le premier programme

Le voici, le voilà, la hantise de tous les débutants : le fameux "Hello World!". En voici le source :

#include 

int main(){
    printf("Salut tout le monde.\n");

    return 0;
}

On compile, et on l'exécute depuis un Shell : "Ça marche !".

C - premiers pas

Génial, on a réussi à compiler un programme tout con, que même un PC sait faire ! Et en plus ça fonctionne...

Décidément, on se moque vraiment de notre gueule ! N'importe quoi !
Que nenni ! Exécutez-le maintenant depuis le Workbench... Juste pour voir !

Trois possibilités :
  • Vous utilisez DICE : il ne se passe... rien (décidément, c'est vraiment du vol cet article !).
  • Vous utilisez le Lattice ou GCC : une fenêtre s'ouvre affichant votre fameux message.

    C - premiers pas

  • Vous n'avez pas de chance, visite du Guru. Je crois que c'était le cas avec l'Aztec C.
Explication : comme vous le savez, le "printf" va afficher ses arguments sur le flux de sortie standard, le très connu "stdout". Le problème est que si le programme est lancé depuis le Workbench, ce stdout n'existe pas (eh oui, on les affiche où ces messages ?), d'où le plantage dans le cas de l'Aztec. Le SAS et GCC ont contourné le problème en créant une fenêtre de sortie. Bon, c'est une bonne idée mais cela n'explique pas pourquoi un simple...

int main(){
    return 0;
}

...génère déjà un exécutable de plusieurs ko : il faut du code pour la gérer cette fenêtre !

Regardez bien la documentation de votre compilateur, elle vous indiquera comment le supprimer et diminuer ainsi la taille de votre exécutable.

DICE, lui, a créé deux entrées dans le programme : le classique "main()" lorsqu'il s'exécute depuis un Shell ou un "wbmain()" lorsque c'est depuis le Workbench. Si l'utilisateur n'en fournit pas une, il utilise celle par défaut qui ne fait que terminer le programme. C'est elle qui s'exécute dans notre cas.

Moralité : dans un programme devant s'exécuter depuis le Workbench, de "printf()", "puts()" et dérivés tu n'utiliseras.

Notre Guru à nous

Après avoir fait ce que tout le monde sait faire, innovons et créons notre propre générateur de... Guru. La fonction "DisplayAlert()" permet, en effet, de générer un écran noir, avec un cadre rouge ou jaune (à partir du Kickstart 2.0 uniquement), avec le ou les message(s) de notre choix. Voici le listing tant attendu :

#include <exec/types.h>
#include <clib/intuition_protos.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main(int ac, char **av){
	if(ac<2){
		puts("Syntaxe : MonGuru 'message'");
		exit(EXIT_FAILURE);
	} else {
		int longueur=strlen(av[1]);
		char buff[longueur + 5];    /* Buffer de travail */

			/* Initialisation du buffer */
		*((short *)buff) = 50;      /* position en abscisse (X) */
		buff[2] = 17;               /* position en ordonnées (Y) */
		strcpy(&buff[3],av[1]); /* Copy du message */
		buff[4+longueur]=0;         /* Rien d'autre a afficher */

			/* Notre Guru */
		DisplayAlert(RECOVERY_ALERT,buff,30);

		exit(EXIT_SUCCESS);
	}
}

Évidemment, on ne va pas programmer comme des porcs, les deux premières lignes permettent d'inclure le prototype de "DisplayAlert()" ainsi que les définitions associées. En particulier, notez bien le premier <exec/types.h>, qui définit tous les types couramment utilisés par le système. Les autres includes sont classiques.

Le prototype de la fonction est le suivant :

BOOL DisplayAlert( unsigned long type, UBYTE *message, unsigned long hauteur );

avec...
  • type : type de la requête RECOVERY_ALERT (alerte jaune) ou DEADEND_ALERT (alerte rouge). Comme indiqué précédemment, sous 1.x toutes les requêtes seront rouges, mais avec DEADEND_ALERT, la fonction renvoie toujours FALSE, alors qu'avec RECOVERY_ALERT elle renvoie TRUE si on a cliqué sur le bouton gauche, et FALSE si on a cliqué sur le droit.
  • message : le message qui doit s'afficher. Attention, il a un format spécifique. Sur deux octets est d'abord codée la position en abscisse du premier caractère, puis sur un octet l'ordonnée, le tout par rapport à l'origine de l'écran noir, enfin on trouve la chaîne se terminant par un 0. Suit un octet de continuation : s'il est NULL, c'est fini, sinon on retrouve une autre chaîne qui reprend la même structure. Dans notre cas, et pour faire simple, une seule ligne peut être affichée. A vous de faire les modifications nécessaires pour en afficher plusieurs... (eh, faut bosser un peu !).
  • hauteur : hauteur en pixel de l'écran noir.
Vous pouvez maintenant compiler le listing. Si une erreur de compilation s'affiche, l'explication arrive, sinon, lancez-le... après avoir sauvegardé tout ce qu'il y avait à sauvegarder !

Il se peut que cela fonctionne tel quel (!), mais vous avez plus de (mal)chance que cela se termine par le réveil du vrai Guru...

En guise d'explication, une visite au sein des entrailles de l'Amiga s'impose. Comme vous le savez certainement, AmigaDOS n'est pas un bloc monolithique, genre le fameux "kernel" (noyau) cher aux Unix, mais est composé de plusieurs bibliothèques partagées dont chacune a une fonction précise. Si ça plante, il faut regarder la compilation avec GCC pour en trouver la raison.

C - premiers pas

Tiens, manquerait-il quelque chose ? "DisplayAlert()" est une fonction qui fait partie d'intuition.library, et pour l'appeler, le compilateur a besoin de savoir où se trouve cette bibliothèque. Eh oui, contrairement à ce qui se passe avec les machines 8 bits, dans l'Amiga rien (ou presque), n'est fixé à priori. A chaque amorce de la machine, les bibliothèque se placent où le système leur dit. "DisplayAlert()" se trouve donc à une adresse qui est relative à IntuitionBase, que nous devons initialiser, sinon... Guru.

Voici le nouveau listing, qui fonctionne lui !

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>   
	/* Ces 3 là ne nous servent à rien mais évitent */
#include <exec/semaphores.h>
	   /* des erreurs à la compilation */
#include <exec/io.h>       
	   /* dans exec_protos.h */
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

struct ExecBase        *SysBase;
struct IntuitionBase   *IntuitionBase=NULL;

void fini(){
	if(IntuitionBase){
		CloseLibrary((struct Library *)IntuitionBase);
		IntuitionBase = NULL;
	}
}

int main(int ac, char **av){
	SysBase = *(struct ExecBase **)4;   /* Recherche d'exec */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0))){
		puts("Impossible d'ouvrir intuition.");
		exit(EXIT_FAILURE);
	}
	atexit(fini);

	if(ac<2){
		puts("Syntaxe : MonGuru 'message'");
		exit(EXIT_FAILURE);
	} else {
		int longueur=strlen(av[1]);
		char buff[longueur + 5];    /* Buffer de travail */

			/* Initialisation du buffer */
		*((short *)buff) = 50;      /* position en abscisse (X) */
		buff[2] = 17;               /* position en ordonnées (Y) */
		strcpy(&buff[3],av[1]); /* Copy du message */
		buff[4+longueur]=0;         /* Rien d'autre a afficher */

			/* Notre Guru */
		DisplayAlert(RECOVERY_ALERT,buff,30);

		exit(EXIT_SUCCESS);
	}
}

La seule partie logicielle fixe dans le système est l'adresse d'Exec, c'est-à-dire la bibliothèque qui s'occupe du système (allocation mémoire, chargement des bibliothèques et autres...). Eh bien oui, on a beau être relatif à fond, il faut toujours garder une référence.

L'adresse où se trouve Exec est placée à l'adresse 4 de la mémoire et doit être stockée dans une variable nommée "SysBase" (logique, non ?). On peut ensuite ouvrir Intuition.

Apprenez-lui le caniveau

On doit rendre le système comme on l'a trouvé. Libérer la mémoire, fermer les écrans et les fenêtres... sont des choses auxquelles on doit penser.

N'oubliez pas de fermer aussi les bibliothèques ouvertes ! C'est ici le rôle de la fonction "fini()" appelé par un "atexit()". On est ainsi sûr que quoi qu'il se passe, elle sera appelée.

Notez que "OpenLibrary()", outre le nom, demande aussi un entier, qui est le numéro minimum de version de la bibliothèque. Si elle est antérieure, la fonction échoue en renvoyant un NULL. On verra l'utilité plus tard lorsque nous utiliserons des spécificités d'AmigaOS 2.0 ou 3.0. Ici, avec une version à 0, n'importe quelle version fonctionne et ça devrait même marcher avec un Workbench 1.0 !

On notera aussi dans ce listing le cas particulier d'Exec. Nous ne l'avons pas ouverte, mais trouvée grâce à son adresse. Ne jamais tenter de la refermer, sinon... bon vous connaissez la suite !

Inutile de préciser que ce programme ne fonctionne que depuis un Shell, vous l'aurez deviné tout seul. Non ? Bon, d'accord pour cette fois : il y a un "puts()" qui utilise le "stdout". En plus, mais vous ne pouviez pas le savoir, les arguments du Workbench ne sont pas traités de la même façon.

Tout bon programmeur doit être un peu feignant

C'est chiant de devoir ouvrir nous-mêmes les bibliothèques. Beaucoup de compilateurs permettent de les ouvrir et de les fermer automatiquement, sans intervention du programmeur. DICE fut le premier à le proposer et le fait tout seul. En fait, c'est même à nous de lui dire si on ne veut pas que ce soit fait, mais si vous utilisez le mécanisme ci-dessus, il sera prioritaire par rapport à l'auto-ouverture. Pour GCC, il faut lier la bibliothèque "auto" par un "-lauto". Le SAS le permet aussi mais je ne me souviens plus comment.

Dans tous les cas, lisez la documentation de votre compilateur.

Attention : c'est comme tout, en abuser se révèle dangereux, car à force, vous allez oublier de le faire. Et quand il s'agit d'une bibliothèque rajoutée (genre req.library), le compilateur ne saura pas comment se dépatouiller tout seul...

Bon, voilà, c'est fini pour aujourd'hui.

Grand merci à mes très courageux correcteurs : Alain Liverneaux et Alexandre Stotzer.

Questions

Voici quelques questions qui m'ont été posées par mes (très) courageux correcteurs :

Pour quelqu'un qui débute vraiment en C, la ligne "*((short *)buff) = 50;" est obscure

"buff" a été déclaré comme un tableau de caractères, alors qu'il doit contenir en premier un entier court (deux octets) la position en absice du premier caractère. Ici, nous souhaitons que ce soit à partir de la 50e ligne. La solution "crade" aurrait été d'initialiser les octets à la main, c'est-à-dire avec le code suivant :

buff[0] = 0; buff[1] = 50; 

Donc on met 0 dans l'octet de poids fort et 50 dans celui de poids faible, donc 0x256 + 50x1 = 50.

Pour la solution la plus propre, celle employée ci-dessus, la première étape est de faire croire au compilateur, par un "cast" (short *) que "buff" est un pointeur sur un entier court. La ligne devient donc "*buff = 50;", qui se traduirait par "l'entier court pointé par "buff" prend la valeur 50".

On a la même construction, mais en plus sioux, avec...

SysBase = *(struct ExecBase **)4; /* Recherche d'exec */ 

Ici, le "cast" indique qu'à l'adresse 4 se trouve le pointeur sur un pointeur (donc double indirection !) qui pointe sur une structure ExecBase... Comme résultat, on a SysBase qui contient un pointeur sur la structure en question (ouf).

Comment supprimer les segments "Debug" et "Symbols" ?

Sans entrer trop dans la téchnique, les fichiers exécutables sont découpés en plusieurs partie appelés segments. On y trouve, entre autres, celui du code, "Code", celui des variables initialisées, "Data", des variables non initialisées, "BBS", et d'autres qui servent au relogement en mémoire (ceux qui font que les exécutables puissent se placer n'importe où en mémoire). On trouve aussi les deux qui nous intéressent ici :
  • Le segment "Symbol", comme son nom l'indique, contient les informations qui permettent un débogueur, un assembleur, etc. de faire la correspondance entre les adresses où sont stockées les variables et les fonctions et leur nom.
  • Le segment "Debug", quant à lui, contient des informations de débogage, en particulier la correspondance entre les codes 68000 et les lignes du sources.
Comme on le voit, ces deux segments ne servent strictement à rien lors de l'exécution du programme. Il existe sûrement sur Aminet des logiciels qui permettent de les supprimer, mais je ne les connais pas. Pour ma part, j'utilise le menu "HunkLab" de PowerPacker. Cochez "Remove Symbol Hunks" et "Remove Debug Hunks" puis lancer l'opération par "Process Files ...".

Contrairement à ce qui est dit dans l'article, il ne se passe rien lorsque je lance le HelloWorld depuis le Workbench alors qu'il est compilé avec GCC

Ça dépend de la version de GCC et la manière dont il est configuré. Le mien à la version 2.7.0 et se trouve sur le CD Fred Fish 10. De toute façon, il vaut mieux éviter de lancer l'exécution de programme Shell depuis le Workbench, c'est beaucoup plus prudent !


[Retour en haut] / [Retour aux articles] [Article suivant]