La console sous Windows

AttentionArticle en cours de rédaction.

Introduction

Avant de commencer, expliquons ce qu'est une console. Une console est une fenêtre permettant d'interagir avec l'utilisateur principalement au moyen du clavier. Une console est en fait une zone destinée à ne contenir que du texte. Ce procédé est l'héritage des anciens ordinateurs qui ne permettaient d'afficher que du texte.
Un synonyme de mode console est le mode texte, son contraire est le mode graphique.

Une console, ce n'est pas :

Windows et les consoles

Sous Windows, on peut classer les programmes en 2 catégories : Les premiers seront qualifiés de programmes GUI, les autres de programmes console. Sont-ils si différents ? Ou plutôt que dire des programmes qui utilisent à la fois une console et une fenêtre graphique ? Pour bien situer la limite entre ces deux "mondes", je vais clarifier tout de suite les choses : En bref : En général, sous Windows, les programmes console sont utilisés dans le cas de programmes effectuant une certaine tâche et se contentant d'afficher le résultat. La plupart de ces programmes proposent des options en ligne de commande et modifient leur comportement en conséquence. Mais dès qu'un programme vise un large public, demande une interaction importante avec l'utilisateur ou encore a grande quantité de données à afficher, la solution de l'interface graphique est préférée. Comme toujours, ce qui facilite la vie de l'utilisateur complique celle du programmeur ... :) Il s'agit là de différences plutôt abstraites, ne concernant que le fonctionnement des programmes. Mais il y a aussi une différence plus réelle, au niveau physique (dans le fichier exécutable) entre autre. Oui, il y en a une ! Celle-ci se situe au niveau d'un champ particulier du format PE, le format utilisé pour stocker les fichiers exécutables. Il s'agit du champ SUBSYSTEM situé dans le PE Header (=dans l'entête du fichier). La valeur de ce champ est fixé lors de l'édition de liens, à l'aide du paramètre /SUBSYSTEM sous VC++.
On peut donc parfaitement savoir si un programme est un programme GUI ou un programme console. C'est même très simple. Ce champ peut avoir plusieurs valeurs, parmi elles, les 2 plus courantes : La valeur de ce champ va modifier la manière dont Windows va créer le processus. S'il vaut IMAGE_SUBSYSTEM_WINDOWS_GUI, Windows ne va rien faire de particulier. Si un programme veut une console, il faut qu'il se la crée. S'il vaut IMAGE_SUBSYSTEM_WINDOWS_CUI, Windows créé automatiquement une console pour le programme qui peut directement l'utiliser. Windows se charge aussi de fermer cette console une fois le processus terminé. Ainsi, même si vous n'affichez / saisissez rien du tout, il y aura quand même une console visible tout au long de l'exécution de votre programme. Un détail important est que si l'utilisateur ferme cette console, il entraînera la fin de votre programme.

Comment un programme console fait-il pour pouvoir utiliser la console que Windows a créé ?

Lorsque vous faîtes un printf, la fonction s'occupe d'écrire du texte sur la sortie standard à savoir stdout. De même, un scanf va lire depuis stdin.
Mais vous êtes-vous déjà demandé comment étaient initialisés stdin, stdout et stderr ?
En effet, rien dans votre code ne se charge d'initialiser ces descripteurs qui sont pourtant automatiquement disponibles et valides pour tout programme console.
Automatiquement ? Pas si sûr. Cela ne se passe pas dans le main, ni après d'ailleurs (ce n'est pas printf qui s'occupe de l'initialiser). Cela se passe avant votre main. Et oui, cela est possible.
Avant le main, il y a une routine d'initialisation que votre compilateur se charge de placer à l'insu de votre plein gré. Et c'est cette routine qui se charge d'initialiser stdin, stdout et stderr à l'aide des valeurs que lui fournit directement Windows.
Cette routine fait d'ailleurs bien plus que cela. Elle s'occupe par exemple de récupérer la ligne de commande et d'initialiser argc et argv en fonction de son contenu. Elle effectue aussi plusieurs autres opération qui lui sont propres, comme initialiser la gestion mémoire, etc ...
Et enfin, cette routine se charge d'appeler main en lui passant les arguments qu'elle a mis en forme.
Une fois main exécutée, elle se charge du nettoyage (désallouer la mémoire que vous avez oublié de libérer, fermer les fichiers restés ouverts, ...) et stoppe l'exécution du processus en renvoyant à l'OS le code que vous avez renvoyé dans votre main.
Mais où se trouve cette routine ?
Je vous l'ai dit, cette routine est livrée avec votre compilateur. Il n'y a donc pas de cas général, mais que du cas par cas.
Enfin presque. Cette routine d'initialisation fait partie de la librairie standard du C. Il vous faut donc la rechercher dans le code source de l'implémentation de la librairie standard livrée avec votre compilateur, en particulier dans un fichier souvent nommé crt0.h. CRT signifie C Runtime Librairy et désigne la librairie standard.
Chaque compilateur dispose en effet de sa propre implémentation.
Si les headers tels que stdio.h et les fichiers objets associés sont toujours livrés avec, il n'en va pas forcément de même avec les sources.
Pour Microsoft Visual Studio 6, les sources sont disponibles (mais ne sont pas installées par défaut). Elles se trouvent dans le répertoire VC98\CRT. La récupération des handles standards se fait dans le fichier ioinit.c, fonction _ioinit. La routine d'initialisation quant à elle s'appelle mainCRTStartup et se trouve dans crtexe.c.
Comment la routine d'initialisation récupère-t-elle les handles stdin, stdout et stderr que Windows a créé ?
Cela est fait à l'aide de la fonction GetStartupInfo qui remplit une structure STARTUPINFO.
Attention cependant, stdin, stdout et stderr de la librairie standard ne sont pas des handles sur votre console, ce sont des FILE*. Vous ne pouvez donc pas les utiliser avec les fonctions de l'API win32 dédiées aux consoles.

Manipuler la console

Pour manipuler une console, il faut recourir aux fonctions dédiées de l'API Win32.
Celles-ci ont besoin d'un handle sur la console à personnaliser. Hors, même dans le cas d'un programme console disposant dès son lancement d'une console, ce handle n'est pas disponible et doit être récupéré.
Donc, la seule différence entre un programme GUI et un programme console pour la suite des évènements est que le programme GUI doit se charger de créer une console et de la détruire une fois qu'il n'en a plus besoin.

Créer une console

AllocConsole créé une console, initialise les handle stdin, stdout , stderr et l'associe au processus.
Un processus ne peut avoir qu'une seule console.

Détruire une console

FreeConsole

Récupérer un handle sur la sortie standard

Pour modifier le comportement de la console, il est nécessaire d'avoir un handler sur la sortie standard.
En programmation Win32, celui-ci est obtenu via un appel à GetStdHandle.
HANDLE hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
Il est aussi possible de récupérer ce handle depuis stdout ou cout. Les fonctions à utiliser sont spécifiques à chaque bibliothèque standard fournie avec les différents compilateurs. Sous Windows, il s'agit généralement de celle de Microsoft à savoir msvcrt.dll (cas de VC++, LCC). Celle-ci permet d'obtenir le handle système associé au handle interne de la bibliothèque au moyen de _get_osfhandle.
HANDLE hStdOut = (HANDLE)_get_osfhandle( stdout );

Modifier la couleur du texte affiché

SetConsoleTextAttribute(hStdOut, attributs);
attribut peut être une combinaison des flags suivants :
couleur du texte :
couleur de fond :
A partir du bleu, rouge, vert, et de l'attribut couleur intense, on peut obtenir les 16 couleurs du mode VGA (exemples avec FOREGROUND, mais biensûr identiques avec BACKGROUND) :

CouleurFlags
noir
0 (aucun flag)
blanc
FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
bleu
FOREGROUND_BLUE
rouge
FOREGROUND_RED
vert
FOREGROUND_GREEN
marron
FOREGROUND_RED | FOREGROUND_GREEN
fuchsia
FOREGROUND_RED | FOREGROUND_BLUE
cyan
FOREGROUND_GREEN | FOREGROUND_BLUE
gris
FOREGROUND_INTENSITY
blanc intense
FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY
bleu vif
FOREGROUND_BLUE | FOREGROUND_INTENSITY
rouge vif
FOREGROUND_RED | FOREGROUND_INTENSITY
vert vif
FOREGROUND_GREEN | FOREGROUND_INTENSITY
jaune
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY
fuchsia vif
FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY
cyan vif
FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY

Intercepter la fermeture de la console

Chaque console créée par Windows se voit associée une fonction d'interception du signal de fermeture de console par défaut. Cette fonction termine le programme s'exécutant dans la console. Elle est appelée suite à un CTRL-C, un CTRL-BREAK, une fermeture de la console par l'utilisateur (par le menu ou le gestionnaire de tâches), une fin de session (déconnexion de l'utilisateur) ou un arrêt/redémarrage de l'ordinateur.
Pour empêcher cela, il faut remplacer cette fonction (handler routine) par défaut à l'aide de SetConsoleCtrlHandler.
Cette fonction doit avoir le prototype suivant : BOOL HandlerRoutine( DWORD CtrlType );
Le paramètre CtrlType permet de connaître l'origine de la demande de fermeture du programme.
La fonction renvoie un booléen indiquant si elle a traîté le signal ou non. Si ce n'est pas le cas, le gestionnaire par défaut sera appelé et provoquera la fin du processus.
L'appel d'un gestionnaire provoque est effectué dans un thread séparé du thread principal, spécialement créé pour gérer l'interruption. Une application console, typiquement monothreadée, peut donc devenir multithreadé. Cela peut provoquer de mauvaises surprises, en particuliers avec les programmes C standards qui sont monothreadés. Pour éviter les problèmes, la libraririe C standard ne prend pas en compte le signal SIGINT sous Windows. Effecuer un appel à signal dans cette optique sera donc sans effet. Il en est de même avec les signaux SIGILL, SIGSEGV, et SIGTERM qui ne sont jamais générés sous Windows NT.

Changer le titre de la console

Ceci est fait au moyen de la fonction SetConsoleTitle.

Récupérer un handle sur la fenêtre de la console

Jusqu'à Windows 2000, Win32 ne disposait pas de fonction permettant d'obtenir le handle de la fenêtre de la console rattachée à son processus.
Il faut alors recourir à FindWindow. Ce n'est cependant pas si simple car la classe des fenêtres de consoles n'est pas la même sous NT ou Win9x ("ConsoleWindowClass"/"tty"), et il faut identifier le titre de la console associée à son programme.
Un article de la base de connaissances traîte de ce point et donne une solution.
Si vous ne visez que Windows 2000 et supérieur, vous pouvez utiliser GetConsoleWindow.

Afficher la console en plein écran

fullscr.txt

Rediriger les flux d'Entrée/Sortie de sa console

Rediriger les flux d'E/S de sa propre consle est assez facile. Il suffit d'appeler SetStdHandle en précisant le flux à rediriger (STDIN, STDOUT, STDERR) et le handle de redirection à utiliser (typiquement un fichier ou un pipe).

Rediriger les flux d'Entrée/Sortie d'une autre console

Rediriger le flux d'une autre console (donc de celle d'un autre processus) nécessite de recourir à un pipe.
Si vous redirigez l'ensemble des flux d'une console a déjà été créée, cette dernière restera visible, bien qu'elle perde toute utilité.
Si vous voulez faire disparraître la console d'un programme CUI et interragir avec lui en regirigeant ses flux (typiquement pour piloter un programme CUI depuis un programme GUI), c'est à votre programme de créer le processus CUI en lui faisant hériter vos propres flux ce qui ne provoquera pas la création d'une console.
A suivre ...


Sommaire