Home   Archives    Hardware   Programmation   Challenges   Forum   Le Mag    Faq   La Communauté   L'Equipe
 
L'équipe progG de Ti-Fr

Voici les nicks de vos serviteurs ;-)

chickensaver_john
Kevin Kofler
Thibaut
janjan2
TiMad
Iceman89
fréka
Zewoo
Squale92
Verstand
ZdRUbAl
UtOpIaH

et nEUrOne ...

Une idée ?
Une idée pour améliorez cette partie? ou tout simplement vous souhaitez participer à l'élaborations des tutos.
Mailez-nous ici

Les raccourcis
 

Chapitre XXIII


    Dans ce Chapitre, nous verrons à quoi correspond le terme de "Librairie Dynamique", puis nous travaillerons sur la démarche qu'il faut adopter afin d'en créer une en C sous TIGCC, et, naturellement, nous étudierons comment il nous sera possible de l'utiliser, à notre avantage...
    Ne vous inquiétez pas : le Chapitre parait long au premier abord, mais, en fait, il n'occupe une telle place que parce que j'ai essayé de détailler au maximum mes explications... et parce que j'ai pris des exemples qui ne sont pas particulièrement courts :-) !

 

I:\ Généralités sur les librairies dynamiques :

    Avant tout, je vais vous demander d'excuser une petite "erreur de traduction" de ma part... En effet, ce que nous appelons ici librairie porte, en anglais, le nom de "library" (Et "libraries" au pluriel). La traduction exacte de ce terme, adaptée à la programmation serait "Bibliothèque partagée". Mais, par (mauvaise) habitude, nous n'emploierons jamais le terme de "bibliothèque" : nous utiliserons, comme nous l'avons déjà fait, celui de "librairie", qui est plus répandu dans la communauté française des TI-89 et TI-92plus !

    Sans doute avez vous déjà installé un Kernel sur votre calculatrice, ne serait-ce que pour utiliser certains jeux. Si tel est le cas, vous avez pu remarquer la présence programmes présentés comme étant de type ASM dans le VAR-LINK, mais apparaissant avec l'extension LIB dans un Explorer (Tels, pour ne citer que les plus connus et les plus utilisés, Doors-Explorer, ou PCT...).
    Les librairies dynamiques qu'il est le plus courant de rencontrer sur nos TIs sont GraphLib (Qui regroupe de nombreuses fonctions graphiqes), Gray4Lib (Qui permet d'utiliser des graphismes en 4 niveaux de gris, et se base principalement sur GraphLib), FileLib (Qui permet la manipulation de fichiers de la VAT), Userlib (Qui permet d'utiliser bon nombre de diverses fonctions, telles attendre un appui sur une touche, ou encore demander un mot de passe), ZipLib (Qui permet de compresser/décompresser des variables)... et bien d'autre encore !
    A titre informatif, vous pouvez retenir que ce type de librairies correspond aux fichiers comportant l'extension .DLL sous MS Windows.

    Il vous faut tout d'abord savoir que ce qui a donné à ce type de librairies leur qualificatif de "dynamiques" est le fait qu'elles sont appelées par vos programmes, via le Kernel, qui effectue une relocation des librairies. (Nous ne nous intéresserons pas ici à cette partie théorique, qui n'est d'aucune influence sur leur fonctionnement, et à laquelle je n'ai jamais vraiment prêté attention... !).
    Donc, pour pouvoir utiliser ce type de librairie à partir d'un de vos programmes écrit en C, il sera impératif que celui-ci ait été écrit en mode Kernel, et non pas en mode Nostub.
   Si vous souhaitez apprendre plus de détails sur la façon dont le Kernel traite les librairies lors de leur utilisation par un programme, vous pouvez visiter cette page écrite par François REVOL, a.k.a mmu_man sur le forum de TI-Fr : http://clapcrest.free.fr/revol/ti68k/tuto_libs.html. Je le remercie d'ailleurs de l'autorisation qu'il m'a donné de placer ce lien ici.

    Nous allons à présent voir en quoi nous pouvons considérer que les librairies dynamiques sont utiles, c'est-à-dire que nous essayerons d'énumérer leurs avantages, puis nous tenterons de voir quels sont leurs inconvénients :

  • Avantages et utilité des librairies dynamiques :
    • Les librairies dynamiques permettent en premier lieu de partager des fonctions utilisées par de nombreux programmes. C'est le rôle que remplissent la majeure partie des librairies présentes sur votre calculatrice. Elles permettent au programmeur d'utiliser les fonctions qu'elles comportent, sans se soucier de leur fonctionnement.
    • Elles permettent aussi de regrouper des données, telles des images, ou des records, dans un fichier différent de celui du programme principal.
    • Enfin, elles peuvent, dans certains cas, aider à des facilités de traduction d'un programme. En effet, imaginons le cas d'un  programmeur qui veux écrire son programme en plusieurs langues, mais sans le réécrire intégralement pour chacune de ces langues... Il lui suffira alors de créer un librairie qui contiendra toutes les chaînes de caractère de son programme. Ensuite, il n'aura plus qu'à distribuer différentes versions de cette librairie, à raison de une par langue.
  • Inconvénients inhérents à l'utilisation de librairies dynamiques :
    • Pour utiliser ce type de librairies, il est INDISPENSABLE de travailler en mode Kernel (Je sais que de plus en plus de programmeurs préfèrent la mode Nostub, mais, puisque les librairies ont besoin d'un Kernel pour fonctionner, il est impossible de faire autrement !)
    • Il faut avoir plusieurs fichiers sur sa calculatrice... au minimum, le programme en lui-même, et la librairie qui lui correspond...
    • Enfin, pour pouvoir modifier la librairie, par exemple en y enregistrant des records, il faut que celle-ci soit désarchivée.

 

II:\ Création de la librairie dynamique sous TIGCC :

        A: Opérations préliminaires :

    Pour créer une librairie dynamique avec TIGCC, il vous faut créer un nouveau projet. Ici, nous utiliserons un projet nommé "tcilib1", qui donnera, comme pour les programmes normaux, le nom du fichier qui sera présent sur la TI.
    Ensuite, il vous faut créer un nouveau fichier comportant l'extension .c, exactement de la même façon que vous pourriez créer un nouveau code source vierge.

    Maintenant, il vous faut effacer les diverses informations ou fonctionnalités que TIGCC a placé par défaut dans ce nouveau ficher. Pour cela, sélectionnez tout, et appuyez sur Del !
    Vous avez donc une page entièrement vide devant vous... Nous allons pouvoir commencer à la remplir :-) !

    Pour commencer, je vous conseille fortement d'inclure les librairies de TIGCC, qui vous permettront d'utiliser des fonctions déjà écrites. De plus, il faut que vous précisiez un mode de compilation. Naturellement, ne compilez pas votre librairie en mode nostub ! Incluez donc les deux lignes de code suivantes dans votre source :
        #define USE_KERNEL    // Permet la compilation du programme en mode Kernel
        #include <tigcclib.h>   
// Inclut toutes les librairies de TIGCC.

    Pour déclarer un fichier .c comme une future librairie une fois sur la TI, il faut placer à l'intérieur de celui-ci cette ligne de code :
        char _library[] = "Un commentaire quelconque...";
    Comme vous pouvez le voir, cela ressemble fortement au type de commentaire qu'il est possible d'inclure avec tout programme écrit en mode Kernel !

    Ensuite, il vous faut déclarer au compilateur pour quelle calculatrice il devra créer la libraire. cela se déroule exactement de la même façon que pour les programmes normaux, c'est-à-dire qu'il vous faudra inclure ce type de ligne de code :
        short _ti92plus, _ti89;

        B: Le code de la librairie en lui-même :

    Maintenant que nous avons vu comment faire pour déclarer une librairie, nous pouvons commencer à nous intéresser à la partie qui concerne le code de la librairie à proprement parler...

    Chaque fonction de la librairire doit être introduite par ce type d'instruction :
        #define nom_de_la_fonction nomlib__numérodelafonction
    Ainsi, puisque nous prendrons l'exemple d'une librairie nommée "tcilib1", il faudra déclarer chaque instruction ainsi :
        #define nom_de_la_fonction tcilib1__xxxx
    Attention : la numérotation de xxxx pour les fonctions ou variables commence à 0000, et croit ensuite : 0001 puis 0002, et ainsi de suite...

    Il convient ensuite de placer le code de la fonction, exactement de la même façon que nous le ferions dans un programme normal en C. Naturellement, les fonctions utilisées peuvent être de tous types (void, short, long, ...), et prendre autant d'arguments que vous le souhaitez !

    Je peux vous conseiller de définir la première fonction de la librairie comme son numéro de version, ce qui peut éventuellement apporter une aide par la suite, si vous en faites plusieurs versions successives... Ainsi, dans notre exemple, la première fonction, ou plutôt, le numéro de version serait défini de cette façon :
        #define tcilib_version tcilib1__0000
        long tcilib1_version = 0b0000000000000001;

    Ensuite, il vous suffit de définir les autres fonctions de la librairies de la même façon. Dans notre cas, la seconde fonction que nous utiliserons est un exemple que j'ai extrait de la FAQ de TIGCC. Il s'agit d'une fonction donnée par Zeljko Juric pour tracer des lignes plus rapidement qu'avec DrawLine. Je l'ai un peu modifiée, afin qu'elle ai plus de possibilités, mais son mode de fonctionnement est équivalent, ou presque, même si cela a entraîné une baisse de vitesse. Voilà comment nous inclurions cette fonctions dans la librairie :
#define DrawLineFast tcilib1__0001
void DrawLineFast(short x1, short y1, short x2, short y2, short mode)
{

/* Liste des Arguments :
x1 : coordonnées en X du point de départ.
y1 : coordonnées en Y du point de départ.
x2 : coordonnées en X du point d'arrivée.
y2 : coordonnées en Y du point d'arrivée.
mode : Le mode selon lequel la ligne sera tracée. mode peut prendre pour valeurs : A_NORMAL, A_REVERSE, A_XOR.
*/

 
short x = x1, y = y1;
 
short dx = abs (x2 - x1), dy = abs (y2 - y1);
 
short ystep = (y1 < y2) ? 1 : -1, pystep = 30 * ystep;
 
short mov = dx ? 0 : -1;
 
unsigned char *ptr = (char*)(void *)LCD_MEM + 30 * y + (x >> 3);
 
short mask = 1 << (~x & 7);
 
if (x1 < x2)
   
while (x != x2 || y != y2)
    {
     
if(mode == A_NORMAL)
        *
ptr |= mask;
     
if(mode == A_REVERSE)
        *
ptr &= mask;
     
if(mode == A_XOR)
        *
ptr ^= mask;
     
if (mov < 0) y += ystep, ptr += pystep, mov += dx;
     
else
     
{
       
mov -= dy;
       
if (++x & 7) mask >>= 1;
       
else ptr++, mask = 0x80;
      }
    }
 
else
    while
(x != x2 || y != y2)
    {
     
if(mode == A_NORMAL)
        *
ptr |= mask;
     
if(mode == A_REVERSE)
        *
ptr &= mask;
     
if(mode == A_XOR)
        *
ptr ^= mask;
     
if (mov < 0) y += ystep, ptr += pystep, mov += dx;
     
else
     
{
       
mov -= dy;
       
if (x-- & 7) mask <<= 1;
       
else ptr--, mask = 1;
      }
    }
}

    Il est aussi possible d'utiliser une librairie dynamique pour retenir le nom du détenteur d'un record, par exemple. pour cela, il faut déclarer dans cette librairie une chaîne de caractères qui le contiendra. Voilà comment nous pourrions effectuer cette opération :
        #define tcilib1_chaine1 tcilib1__0002
        char tcilib1_chaine1[15] = "";
    Ce faisant, nous avons déclaré dans notre librairie une chaîne de caractères nommée tcilib1_chaine1, et comportant 15 caractères. Attention : Remarquez bien la présence des guillemets et du signe d'égalité dans lé déclaration de la chaîne, qui indiquent au compilateur et au programme que la chaîne ne doit pas être allouée sur le Stack (ou sa durée de vie serait courte !), mais à l'intérieur de la librairie !
    N'oubliez pas que, pour que la chaîne de caractères puisse être modifiée, il faut que la librairie soit désarchivée... Dans le cas contraire, le propriétaire du record, par exemple, ne verra pas son nom mémorisé...

    Nous utiliserons aussi une autre fonction, elle aussi issue de la FAQ de TIGCC, et je lui ai apporté de petites modifications (Mon but n'est pas ici de développer de nouvelles fonctions, mais de vous montrer comment les libraires dynamiques fonctionnent => je ne prends pas la peine d'en créer !). Celle-ci sera celle qui est donnée par Zeljko Juric pour permettre à l'utilisateur d'entrer une chaîne de caractères dans le programme. Nous la définirons, bien entendu, de la même façon que la fonction DrawFastLine, c'est-à-dire de la façon suivante :
#define InputStrXY tcilib1__0003
void InputStrXY(short x, short y, char *buffer, short maxlen)
{
/* Liste des Arguments :
x : coordonnées en X du point où la demande apparaîtra à l'écran.
y : coordonnées en Y du point où la demande apparaîtra à l'écran.
buffer : la chaîne de caractères où le résultat sera stocké.
maxlen : le nombre maximal de caractères qu'il sera possible d'entrer.
*/
 
SCR_STATE ss;
 
short key, captured, i=0;
 
void CaptureHandler (EVENT *ev)
  {
   
if(ev->Type == CM_STRING)
     
captured = *(ev->extra.pasteText);
  }
 
MoveTo(x, y);
 
buffer[0] = 0;
 
SaveScrState(&ss);
 
do
 
{
   
MoveTo(ss.CurX, ss.CurY);
   
printf("%s_  ", buffer);
     
// Note that two spaces are required if F_4x6 font is used
   
key = ngetchx();
   
if (key == KEY_CHAR && i < maxlen)
    {
     
EVENT ev;
     
ev.Type = CM_KEYPRESS;
     
ev.extra.Key.Code = key;
     
EV_captureEvents(CaptureHandler);
     
EV_defaultHandler(&ev);
     
EV_captureEvents(NULL);
     
buffer[i++] = captured;
    }
   
if (key >= ' ' && key <= '~' && i < maxlen) buffer[i++] = key;
   
if (key == KEY_BACKSPACE && i) i--;
   
buffer[i] = 0;
  }
while(key != KEY_ENTER);
}

        C: Opérations finales :

    Voilà, vous avez terminé l'écriture du source de votre librairie dynamique. A présent, il ne vous reste plus qu'à la compiler, afin d'en faire un fichier que vous pourrez par la suite envoyer sur la calculatrice. Pour cela, il vous faudra agir exactement de la même façon que pour tous les autres programmes écrit en C que vous ayez jusqu'à présent compilé : le fait que ce soit une librairie ne change rien : TIGCC va créer un fichier comportant l'extension .9xz ou .89z, et vous pourrez ensuite l'envoyer à votre TI-92plus ou TI-89.

    Si vous souhaitez consulter le code de la librairie dans son intégralité plutôt que sous forme de multiples "petits morceaux" comme ici, je vous conseille de cliquer sur CE lien.

 

III:\ Le fichier Header qui permettra d'utiliser la librairie :

    Pour utiliser une libraire dynamique, il vous faut, comme pour toutes les librairies de TIGCC, un fichier header (comportant l'extension .h). Celui-ci devra par la suite être inclus dans le fichier comportant le code source du programme utilisant la librairie. (Nous verrons comment faire au IV de ce Chapitre).

    Ce que je vous conseille de faire est, pour des raisons de facilités de navigations, de placer directement de fichier header dans votre projet TIGCC en cours (celui que vous utiliserez pour le programme.). ATTENTION : il ne doit en aucun cas s'agir du projet correspondant au code de la librairie !
    Il vous est aussi possible d'enregistrer ce fichier header dans le répertoire Include\C de TIGCC...
    Nous verrons dans la partie suivante de ce Chapitre comment faire pour l'utiliser dans ces deux cas, mais sachez que je préfère la première solution, car elle permet de regarder le code du header de temps en temps pendant l'écriture du programme.


        A: Les bases communes à tous les headers :

    Le fichier Header doit comporter des instructions qui serviront au préprossesseur. Étant donné qu'il n'est pas vraiment nécessaire de connaître leur mode de fonctionnement, nous ne l'étudierons pas en détails ici : nous nous contenterons de le commenter légèrement, afin que vous compreniez tout de même à quoi sert ce que vous écrivez !
    Voici ce qu'il ne vous pas utile de connaître, mais qui est tout de même nécessaire dans le Header :

#ifdef DOORS           // Si le programme est créé en mode Kernel...
#ifndef __H_testlib    // Si le fichier Header n'est pas défini
#define __H_testlib    // Il faut le définir => exécution des actions du code du Header...

// Ici, vous devrez insérer ce que nous appellerons dorénavant le code du Header...

#endif    // Fin du test de la condition "Si le Header n'est pas défini".
#else     // Si le programme n'est pas créé en mode Kernel...
#error: Pour utiliser des librairies dynamiques, vous devez être en mode KERNEL !!!    // Affiche un message d'erreur...
#endif    // Fin du test de la condition "Programme en mode Kernel"

    Je vais tout de même vous fournir quelques explications, même si elle ne seront que assez superficielles :

  • La boucle conditionnelle qui teste si le programme est défini en mode Kernel ou non permet d'éviter tout problème par la suite. En effet, pour avoir la capacité d'utiliser des librairies dynamiques, un programme DOIT être créé en mode Kernel, et non pas en mode Nostub.
  • La boucle conditionnelle qui teste si le Header est déjà défini permet d'éviter les inclusions multiples. Ici, nous supposerons que le fichier Header est nommé testlib.h !


        B: Le "code" du Header :

    Dans celui-ci, il faut, pour chaque fonction, définir son nom, en le faisant correspondre au numéro auquel la fonction correspond dans la librairie. Il convient ensuite de donner le prototype de la fonction.

    Une particularité qu'il ne faut pas oublier, c'est que les fonctions ou variables déclarée dans la librairie n'appartiennent pas au programme de la même façon que toutes celles que nous avons jusqu'à présent utilisé... Il sera donc impératif de les déclarer de type extern, et incluant ce modificateur de type devant celui que nous leur avons attribué dans le code de la librairie.

    Dans les cas où il ne s'agit pas d'une fonction, mais d'une variable; il convient de faire de même. Ainsi, dans le cas du numéro de version de notre librairie, nous aurons ceci :
        #define tcilib1_version tcilib1__0000
        extern long tcilib1_version;
    En effet, cette variable correspond à la première "fonction" de la librairie, et porte donc le numéro 0000. De plus, il d'agit d'une variable de type long.

    Pour les fonctions que nous avons déclaré dans notre librairie, il conviendra d'utiliser (dans le cas de la fonction DrawFastLine) :
        #define DrawFastLine tcilib1__0001
        extern void DrawFastLine(short, short, short, short, short);
    En effet, cette fonction correspond à  la fonction portant le numéro 0001 dans la librairie, et elle prend en arguments quatre shorts.

    De la même façon que pour le numéro de version, la chaîne de caractères intégrée à la librairie doit être définie comme ceci :
        #define tcilib1_chaine1 tcilib1__0002
        extern char tcilib1_chaine1[];
    En effet, cette variable porte le numéro 0003 dans la librairie, et il s'agit bien d'une variable de type tableau de chars.

    Enfin, pour la dernière fonction que nous avons intégré à notre librairie, il faudra, puisque c'est celle qui porte le numéro 0003, et qu'elle prend en arguments deux shorts, un pointeur vers un char, et un autre short, écrire ceci :
        #define InputStrXY tcilib1__0003
        extern void InputStrXY(short, short, char *, short);

 

    De la même façon que pour le source de la librairie, si vous souhaitez consulter le code du fichier header dans son intégralité plutôt que sous forme de multiples "petits morceaux" comme ici, je vous conseille de cliquer sur CE lien.

 

IV:\ Le code du programme utilisant la librairie :

        A: Utilisation du fichier Header :

    Nous avons dit précédemment qu'il était possible d'enregistrer le fichier Header de deux manières différentes :

  • Dans le répertoire TIGCC\Include\C    => Vous devrez mettre au début de votre programme, ceci :
            #include >testlib.h>
    C'est ce que vous faites pour toutes les libraires de TIGCC (même si elles ne fonctionnent pas selon le même principe !).
  • Dans le répertoire où est enregistré votre projet (dans les cas où, par exemple, c'est une librairie que vous ne comptez utiliser que pour un seul programme). => Vous devrez mettre ceci au début de votre programme :
            #include "testlib.h"

        B: Utilisation des fonctions de la librairie :

    C'est la dernière opération à effectuer : Utiliser les fonctions et variables que nous avons défini dans la librairie ! :-)

    Pour cela, il suffit d'agir EXACTEMENT de la même façon que pour toutes les fonctions intégrées aux librairies de TIGCC. En effet, le fichier Header que nous avons créé précédemment correspond au type de headers utilisés par TIGCC pour ses librairies internes.
    Nous pourrons donc, par exemple, effecteur le manipulation suivante pour entrer une chaîne de caractères dans la variable correspondante de la librairie :
        InputStrXY(20, 20, tcilib1_chaine1, 14);
    Et, naturellement, il est aussi possible d'afficher le contenu de celle-ci, en utilisant des instructions "normales" de TIGCC :
        DrawStrXY(20, 20, tcilib1_chaine1, A_NORMAL);
    Pour utiliser la fonction de dessin rapide de lignes, il conviendra de faire quelque chose ressemblant à ceci :
        DrawFastLine(10, 20, 50, 75, A_NORMAL);

    Enfin, voici le programme que j'ai choisi d'utiliser comme exemple. Je vous conseille de l'exécuter au moins deux fois sur votre calculatrice, avec la librairie désarchivée, et, pour voir la différence, d'essayer aussi en ayant archivé la librairie...
#define USE_KERNEL
#define NO_EXIT_SUPPORT
#include <tigcclib.h>

#include "testlib.h"

char _comment[] = "Programme de Démo pour TIClib1";
short _ti92plus, _ti89;

void _main(void)
{
 
short i=0;
 
short lcd_width = LCD_WIDTH;
 
short lcd_height = LCD_HEIGHT;

  ClrScr();
  FontSetSys(F_6x8);
  DrawStrXY(10, 10, "Record 1 ::", A_NORMAL);
  DrawStrXY(20, 20, tcilib1_chaine1, A_NORMAL);
  ngetchx();

 
for (i=0 ; i<lcd_height ; i++)
  {
   
DrawFastLine(0, 0, lcd_width-1, lcd_height-i, A_NORMAL);
   
DrawFastLine(lcd_width-1, 0, 0, lcd_height-i, A_NORMAL);
   
DrawFastLine(0, lcd_height, lcd_width-1, i, A_NORMAL);
   
DrawFastLine(lcd_width-1, lcd_height, 0, i, A_NORMAL);
  }
 
 
ClrScr();
 
for (i=0 ; i<lcd_height ; i++)
  {
   
DrawFastLine(0, 0, lcd_width-1, lcd_height-i, A_XOR);
   
DrawFastLine(lcd_width-1, 0, 0, lcd_height-i, A_XOR);
   
DrawFastLine(0, lcd_height, lcd_width-1, i, A_XOR);
   
DrawFastLine(lcd_width-1, lcd_height, 0, i, A_XOR);
  }
 
 
for (i=0 ; i<lcd_height ; i++)
  {
   
DrawFastLine(0, 0, lcd_width-1, lcd_height-i, A_REVERSE);
   
DrawFastLine(lcd_width-1, 0, 0, lcd_height-i, A_REVERSE);
   
DrawFastLine(0, lcd_height, lcd_width-1, i, A_REVERSE);
   
DrawFastLine(lcd_width-1, lcd_height, 0, i, A_REVERSE);
  }

  ClrScr();
  DrawStrXY(10, 10, "Nouveau record ?", A_NORMAL);
  InputStrXY(20, 20, tcilib1_chaine1, 14);
}

    Le code de ce programme étant des plus banals, mis à part le fait qu'il fait appel à des fonctions non incluses d'origine dans TIGCC, je ne le commenterai pas... Tous son contenu a déjà été étudié précédemment dans ce tutorial. Si jamais vous ne vous souvenez pas de quelques chose, je vous encourage fortement à revenir un peu en arrière, ça ne fait jamais de mal...

 

    Bon, voilà, j'espère que, maintenant, vous avez compris comment faire pour créer et utiliser vos propres librairies dynamiques en C...
    A partir du C, il peut être possible d'utiliser des librairies dynamiques que vous n'avez pas défini vous-même, et qui ont été développées pour l'ASM (Comme ZipLib, par exemple), mais ça demande une bonne compréhension du langage Assembleur, parce qu'il vous faudra quasiment toujours passer par de l'ASM Inline... Désolé, mais c'est comme ça... (Vous pourrez un exemple d'utilisation de ZipLib différent de celui de la FAQ de TIGCC avec une macro ASM dans la FAQ de ce tutorial).

Retour au menu général

Chapitre XXIV


 


Copyright © Ti-Fr v2.0 1999-2002