Utiliser les printf() sous AmigaOS (partie1)
Un article de GuruMed.
par Sara
Je vous propose une petite série d'articles qui traitent de l'utilisation en langage C des entrées/sorties et du multitache dans les applications AmigaLike. Ces articles ne décrivent pas ces aspects de manière exhaustive, mais exposent des cas d'utilisation, et attirent l'attention sur quelques pièges.
On ne rigole pas, c'est plus compliqué que ça en a l'air... ;) D'abord, il faut différencier printf() et Printf(), et ensuite tenir compte des contextes d'appel...
Sommaire |
Fonctions d'entrées/sorties des bibliothèques C et AmigaDos
Les fonctions de la bibliothèque stdio du langage C (printf(), puts(), scanf(), gets(), ...) utilisent les variables globales d'entrées/sorties standard stdin/stdout/stderr. Par contre, les fonctions de la bibliothèque système dos.library (Write(), Printf(), PutStr(), Read(), ...) utilisent les champs pr_CIS/pr_COS des processus desquelles elles sont appelées. Cela a plusieurs impacts :
- les fichiers d'entrée/sortie stdin/stdout et pr_CIS/pr_COS peuvent être différents,
- les fonctions de la dos.library ne sont utilisables que depuis des "Process" (en opposition avec les "Task"),
- les fonctions de la librairie stdio doivent être utilisées avec précautions dans les applications multi-process.
Valeur des flux d'E/S au lancement d'un exécutable depuis le shell ou le WB
Les valeurs de stdin/stdout et pr_CIS/pr_COS au démarrage d'une application dépendent du compilateur utilisé et du code de démarrage qu'il rajoute. Ci-dessous est présenté un exemple de code de démarrage qui initialise les flux (prélevé de newgccstart.lha sur Aminet). Attention, cette archive ne contient que des ébauches de prototypes d'essais, mais ça donne une idée de ce qu'on peut y trouver. ;)
newgccstart.lha
void __initstdio(void)
{
BPTR window;
if(_WBenchMsg!=NULL) // Executable lance depuis le WB
{
if((window=Open("CON:\\AUTO",MODE_NEWFILE))==NULL) // Creation d'une console
exit(RETURN_FAIL);
SelectInput(window); // Redirection des entree/sortie de la dos.library vers cette console.
SelectOutput(window); //
}
stdin->file=Input();
stdout->file=Output();
if((stderr->file=((struct Process *)FindTask(NULL))->pr_CES)==NULL)
stderr->file=stdout->file;
}
Ainsi, on remarque dans cet exemple que si l'exécutable est lancé depuis le workbench, une console est crée, et les flux d'entrée/sortie de la dos.library sont redirigés vers cette console. On constate également que stdin/stdout/stderr de la bibliothèque stdio sont associés aux flux de la dos.library; printf() et Printf() utiliseront donc la même sortie au lancement du programme.
Soit le simple programme :
printf1.c
/* gcc -Wall -noixemul printf1.c -o printf1 -lamiga */
#include <stdlib.h>
#include <stdio.h>
#include <proto/dos.h>
int main(void)
{
printf("printf : Salut les codeurs !\n");
Printf("Printf : Salut les codeurs !\n");
exit(EXIT_SUCCESS);
}
Lancement depuis le shell : On retrouve bien les deux messages dans le shell. Les flux d'entrée/sortie de la dos.library sont initialisés par le cli, les flux sont dirigés vers sa console. Sauf cas particulier, le compilateur génère, comme vu ci-dessus, un exécutable qui associe aussi stdout/stdin vers les flux de la dos.library au démarrage. On a donc les deux messages. Lancement depuis le WB : Une fenêtre de sortie apparait, dans laquelle s'affichent les messages. C'est le compilo qui l'a créée !
Valeur des flux d'E/S au lancement d'un process depuis un exécutable
Les deux chapitres ci-dessous donnent des informations sur l'initialisation des entrée/sortie de Process créés au sein d'applications.
Lancement d'un "Process-Fonction"
La fonction de lancement de Process-fonction utilisée ci-dessous est CreateNewProcTags(). Cette fonction permet de lancer un nouveau processus tout en initialisant les flux d'entrée/sortie du Process créé. Soit le simple programme suivant qui crée un nouveau Process dans lequel printf() et Printf() sont appelés :
printf2.c
/* gcc -Wall -noixemul printf2.c -o printf2 -lamiga */
#include <stdlib.h>
#include <stdio.h>
#include <dos/dostags.h>
#include <proto/dos.h>
void MaTache(void);
int main(void)
{
struct Process *proc = NULL;
proc = CreateNewProcTags(NP_Entry, MaTache, // Point d'entrée du Process
NP_Name, "Mon Process", // Nom du Process
#ifdef __MORPHOS__
NP_CodeType, MACHINE_PPC,
#endif
TAG_DONE);
Delay(50); // On n'a pas interet a terminer le pere avant le fils, sinon, c'est le crash
// (entres autres choses, les flux stdin/stdout seront fermes alors que le process fils s'en sert :( )
// C'est un contournement, ne faites pas ca dans vos programmes ;)
exit(EXIT_SUCCESS);
}
void MaTache(void)
{
printf("printf : Salut les codeurs !\n");
Printf("Printf : Salut les codeurs !\n");
}
Lancement depuis shell et WB : Seul le message "printf" est affiché, car pr_CIS/pr_COS du processus fils ne sont pas définis (NULL), alors que stdin/stdout reste le même pour le Process fils et le Process père...
Soit le même programme avec l'initialisation de pr_CIS/pr_COS pour le Process lancé par CreateNewProcTags() :
printf3.c
/* gcc -Wall -noixemul printf3.c -o printf3 -lamiga */
#include <stdlib.h>
#include <stdio.h>
#include <dos/dostags.h>
#include <proto/dos.h>
void MaTache(void);
int main(void)
{
struct Process *proc = NULL;
proc = CreateNewProcTags(NP_Entry, MaTache, // Point d'entrée du Process
NP_Name, "Mon Process", // Nom du Process
NP_Input, Input(), // Mise a jour du flux pr_CIS pour le processus fils avec celui du pere
NP_CloseInput, FALSE, // Fermeture du flux a la fin du process fils : on ne ferme pas, sinon ça ferme aussi le flux du process pere
NP_Output, Output(), // Mise a jour du flux pr_COS pour le processus fils avec celui du pere
NP_CloseOutput, FALSE, // Fermeture a la fin du process fils : on ne ferme pas, sinon ça ferme aussi le flux du process pere
#ifdef __MORPHOS__
NP_CodeType, MACHINE_PPC,
#endif
TAG_DONE);
Delay(50); // On n'a pas interet a terminer le pere avant le fils, sinon, c'est le crash
// (entres autres choses, les flux stdin/stdout seront fermes alors que le process fils s'en sert :( )
// C'est un contournement, ne faites pas ca dans vos programmes ;)
exit(EXIT_SUCCESS);
}
void MaTache(void)
{
printf("printf : Salut les codeurs !\n");
Printf("Printf : Salut les codeurs !\n");
}
Lancement depuis shell et WB : Affichage de "printf" et "Printf". Chouette, hein ? Les fonctions InPut() et OutPut() de la dos.library retournent les champs pr_CIS/pr_COS du processus père (tels qu'initialisés). CreateNewProcTags() utilise alors ces valeurs pour initialiser les champs pr_CIS/pr_COS du processus fils.
Lancement d'un "Process-exécutable
Ce paragraphe décrit le lancement d'un Process-exécutable, et met en évidence les différences avec le lancement d'un Process-fonction. Soit le simple programme suivant qui lance printf1 :
printf6.c
/* gcc -Wall -noixemul printf6.c -o printf6 -lamiga */
#include <stdlib.h>
#include <stdio.h>
#include <dos/dostags.h>
#include <proto/dos.h>
#include <proto/exec.h>
int main(void)
{
BPTR seg_list = NULL;
struct Process *proc = NULL;
// Chargement de l'executable en memoire : printf1 qui affiche des messages avec Printf() et printf()
// -------------------
seg_list = LoadSeg("printf1");
if(seg_list != 0)
{
// Execution de la tache printf1
// -------------------
proc = CreateNewProcTags(NP_Seglist, seg_list,
NP_Name, "Tache printf1",
//NP_Output, Output(),
//NP_CloseOutput, FALSE,
NP_Cli, TRUE,
NP_Arguments, "",
#ifdef __MORPHOS__
NP_CodeType, MACHINE_PPC,
#endif
TAG_DONE);
Delay(50); // On n'a pas interet a terminer le pere avant le fils, sinon, c'est le crash
// (entres autres choses, les flux stdin/stdout seront fermes alors que le process fils s'en sert :( )
// C'est un contournement, ne faites pas ca dans vos programmes ;)
UnLoadSeg(seg_list);
}
exit(EXIT_SUCCESS);
}
Sous shell et WB : Il n'y a aucun affichage :( De la même manière que pour l'exemple printf2, il n'y pas de "Printf" car pr_CIS/pr_COS du processus fils ne sont pas initialisés. Par contre, contrairement à l'exemple printf2, stdin/stdout du processus fils ne sont pas identiques à ceux du processus père, puisqu'il s'agit du lancement d'un Process-exécutable qui initialise lui-même stdin/stdout. Comme stdin/stdout du processus fils sont initialisés à partir de pr_CIS/pr_COS du processus fils (ici NULL), il n'y a pas de message "printf". Si on décommente les lignes NP_Output et NP_CloseOutput, on obtient les affichages... :)
Modifier la valeur des flux
Ce paragraphe décrit une manière de changer dans ses applications stdin/stdout et pr_CIS/pr_COS.
Soit le simple programme :
printf4.c
/* gcc -Wall -noixemul printf4.c -o printf4 -lamiga */
#include <stdlib.h>
#include <stdio.h>
#include <dos/dostags.h>
#include <proto/dos.h>
void MaTache(void);
int main(void)
{
struct Process *proc = NULL;
proc = CreateNewProcTags(NP_Entry, MaTache, // Point d'entrée du Process
NP_Name, "Mon Process", // Nom du Process
NP_Input, Input(), // Mise a jour du flux pr_CIS pour le processus fils avec celui du pere
NP_CloseInput, FALSE, // Fermeture a la fin du process fils : on ne ferme pas, sinon ça ferme aussi le flux du process pere
NP_Output, Output(), // Mise a jour du flux pr_COS pour le processus fils avec celui du pere
NP_CloseOutput, FALSE, // Fermeture a la fin du process fils : on ne ferme pas, sinon ça ferme aussi le flux du process pere
#ifdef __MORPHOS__
NP_CodeType, MACHINE_PPC,
#endif
TAG_DONE);
Delay(200); // On n'a pas interet a terminer le pere avant le fils, sinon, c'est le crash
// (entres autres choses, les flux stdin/stdout seront fermes alors que le process fils s'en sert :( )
// C'est un contournement, ne faites pas ca dans vos programmes ;)
exit(EXIT_SUCCESS);
}
void MaTache(void)
{
BPTR con_desc = NULL;
BPTR old_output = NULL;
con_desc = Open("CON:80/450/798/250/Alister Output/CLOSE/AUTO", MODE_READWRITE); // Ouverture d'une premiere console
if(con_desc == NULL)
{
printf("cannot open CON:\n");
return;
}
old_output = SelectOutput(con_desc); // Association du flux de sortie de la dos.library (pr_COS) avec la premiere console
freopen("CON:80/450/798/250/Alister Output/CLOSE/AUTO", "w", stdout); // Ouverture d'une deuxieme console, et association avec stdout
printf("printf : Salut les codeurs !\n");
Printf("Printf : Salut les codeurs !\n");
Delay(100);
fclose(stdout);
stdout = NULL;
if(old_output != NULL) (void)SelectOutput(old_output); // Bien remettre l'ancien flux qui sera ferme par le process pere lors de sa terminaison
if(con_desc != NULL) Close(con_desc);
}
Sous shell et WB : Affichage de "printf" et "Printf" dans deux consoles séparées... C'est drôle, hein ? Une variante aurait pu être d'ouvrir les nouveaux fichiers de sortie dans le processus père, et de passer en paramètre à la création du processus fils la nouvelle sortie dos.library. Cet exemple montre egalement qu'il peut être dangereux de jouer avec stdout/stdin dans des applications multitaches, car ces fichiers d'entrée/sortie sont utilisés au niveau "application", et pas "Process".
Le prochain article parlera entre autres des flux d'erreur, des entrée/sortie dans les Task, ... N'hésitez pas à m'envoyer vos remarques !
Stéphane SARAGAGLIA, le 3 Aout 2002 saragagliaMAJUSCULESAVIRER@ifrance.com Mis à jour le 1er Novembre 2003 Mis à jour le 20 Décembre 2003 (orthographe) Mis à jour le 28 Mars 2004 : mel antispam
