|
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
|
| |
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
|
|