Programmation avec la SDL/Lire et écrire dans un fichier
La SDL 2.0 comporte une bibliothèque de lecture et d'écriture dans les fichiers nommée SDL_rwops.h. Celle-ci est plutôt bas niveau et ne présente pas d'avantage majeur par rapport à stdio.h. Elle est cependant opérationnelle et peut être utilisée sans difficulté majeure.
Préparer le contexte d'entrée/sortie
modifierLa structure principale de la librairie qui correspond au FILE de stdio.h est SDL_RWops. Pour obtenir le contexte de lecture et écriture d'un fichier qui sera contenu dans une structure SDL_RWops, nous allons utiliser la fonction SDL_RWFromFile. Voici son prototype :
SDL_RWops* SDL_RWFromFile(const char* fichier, const char* mode);
Cette fonction renvoie un pointeur de SDL_RWops. Nous obtenons donc le contexte de lecture/écriture du fichier souhaité. À présent, étudions ses arguments.
- fichier représente la chaîne de caractère contenant le chemin du fichier dans lequel on veut lire/écrire.
- mode correspond au mode d'utilisation du fichier que l'on souhaite employer. Voici les valeurs qu'il peut prendre :
Mode | Description |
---|---|
r | Lecture seule. Le fichier doit déjà exister. |
w | Écriture seule. Si le fichier n'existe pas, il est créé. Si le fichier existe déjà, son contenu est effacé et l'écriture se fait dans le fichier à présent vide. |
a | Écriture à la fin du fichier. Si celui-ci n'existe pas, il est créé. |
r+ | Lecture et écriture. Le fichier doit déjà exister. |
w+ | Lecture et écriture. Si le fichier n'existe pas, il est créé. Si le fichier existe déjà, son contenu est effacé et l'écriture se fait dans le fichier à présent vide. |
a+ | Lecture et écriture. L'écriture se fait à la fin du fichier. Si celui-ci n'existe pas, il est créé. Vous pouvez repositionner le curseur pour la lecture mais l'écriture se fera toujours à la fin. Après une opération d'écriture, le curseur se trouvera donc à la fin du fichier. |
Il est à noter que pour passer en lecture/écriture binaire, il suffit simplement d'ajouter un "b" aux modes énumérés ci-dessus : "rb", "wb", "ab", "r+b", "w+b", "a+b".
Voici un code, à titre d'exemple, qui va permettre de lire et écrire dans le fichier "exemple.txt" :
SDL_RWops* fichier;
fichier = SDL_RWFromFile("exemple.txt","r+");
Bien sûr, nous l'aurons vu de nombreuses fois au cours de ce livre, lorsqu'on alloue un espace à une structure de la SDL et que nous n'en avons plus besoin, il faut le rendre. Pour cela, nous utiliserons SDL_RWclose. Cette fonction doit être impérativement appelée après avoir employé la fonction SDL_RWFromFile.
Sachez que, si vous souhaitez également utiliser la bibliothèque standard stdio.h, vous pouvez lire ou écrire dans un fichier standard de type FILE avec la fonction SDL_RWFromFP, où FP signifie File Pointer. Voici son prototype :
SDL_RWops* SDL_RWFromFP(FILE* fp, SDL_bool autoclose);
Voici une description des arguments attendus par la fonction :
- fp correspond ici au pointeur du fichier que l'on désire lire/modifier avec SDL_RWops.
- autoclose permet d'appeler une fermeture automatique du contexte SDL_RWops. Si sa valeur est SDL_TRUE ou 1, la fonction SDL_RWclose sera automatiquement appelée à la fin du programme. Au contraire, s'il vaut 0 ou SDL_FALSE, vous devrez appeler cette fonction manuellement.
Voici donc comment passer d'un fichier standard de type FILE en mode r+ à un contexte de lecture/écriture avec la SDL :
#include <SDL2/SDL.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE* fichier = fopen("exemple.txt","r+");
SDL_RWops* fichier_SDL = SDL_RWFromFP(fichier,SDL_TRUE);
return 0;
}
Nous savons à présent ouvrir un fichier avec la SDL. Mais ne manquerait-il pas quelque chose? Lorsqu'on ouvre un fichier, il faut ensuite le fermer afin de libérer la mémoire vive. Pour cela, on peut soit demander à la fonction SDL_RWFromFP de la faire automatiquement comme nous l'avons fait dans le code ci-dessus, soit fermer le fichier manuellement à l'aide de la fonction SDL_RWclose.
int SDL_RWclose(struct SDL_RWops* context);
Cette fonction renvoie une valeur négative en cas d'erreur.
De manière courante, nous utiliseront plutôt SDL_RWFromFile accompagnée de SDL_RWclose à la place de SDL_RWFromFP afin de pouvoir utiliser la SDL indépendamment de stdio.h.
Le curseur
modifierNous savons maintenant ouvrir et fermer un contexte de lecture/écriture de fichier mais, avant d'apprendre à lire et à écrire dans un fichier, nous devons apprendre à naviguer dans celui-ci. Pour cela, la SDL nous met à disposition un curseur virtuel. Celui-ci représente l'endroit où vous êtes en train de lire ou d'écrire dans le fichier.
Position du curseur
modifierPrenons un exemple plus concret. Imaginons que vous souhaitez lire un fichier texte dans lequel se trouve le mot "exemple". Lorsque vous allez demander à la SDL de lire un caractère de ce fichier, elle va lire le premier caractère, c'est à dire 'e'. Ensuite, lorsque vous allez lui demander à nouveau de lire un caractère, elle lira le caractère suivant, donc 'x' et ainsi de suite. Ainsi, le curseur se déplace au fur et à mesure que vous lisez le fichier.
Afin de connaître la position actuelle du curseur nous allons utiliser la fonction SDL_RWtell. Celle-ci est très simple d'utilisation : on passe en argument le contexte de lecture/écriture du fichier et elle nous renvoie un long contenant la position actuelle du curseur.
long SDL_RWtell(struct SDL_RWops* context);
Déplacer le curseur
modifierSeulement, lorsque vous écrivez dans un fichier, vous ne souhaitez pas forcément écrire les caractères les uns à la suite des autres. Vous aurez besoin, à un moment ou à un autre, d'insérer du texte à un endroit précis.
Pour cela, il existe une fonction (en l’occurrence une macro) qui déplace le curseur à l'endroit où vous le souhaitez : SDL_RWseek. En voici le prototype :
Sint64 SDL_RWseek(SDL_RWops* context, Sint64 offset, int whence);
Ici, SDL_RWseek renvoie la position du curseur après qu'il a été déplacé. En cas d'erreur, la fonction renvoie -1. Penchons-nous à présent sur les arguments de la fonction :
- context : le contexte de lecture/écriture
- offset : représente le déplacement du curseur à partir de whence. Lorsqu'il s'agit d'un fichier texte, si offset vaut 1, le curseur passera au caractère suivant. Si on lit en mode binaire, il passera au bit suivant. Offset peut prendre une valeur négative (pour retourner en arrière).
- whence : la position à partir de laquelle on déplace le curseur. Elle peut prendre trois valeurs :
Description | |
---|---|
RW_SEEK_SET | Déplacer le curseur à partir du début du fichier. |
RW_SEEK_CUR | Déplacer le curseur à partir de la position actuelle du curseur. |
RW_SEEK_END | Déplacer le curseur à partir de la fin du fichier (offset doit alors prendre une valeur inférieure ou égale à 0). |
Grâce à cette fonction, on peut ainsi déterminer la taille du fichier, ce qui nous sera utile pour la suite. Essayez de trouver par vous même !
Voici le code qui permet de récupérer la taille du fichier :
#include <SDL2/SDL.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int taille_fichier = 0;
SDL_RWops* fichier = SDL_RWFromFile("exemple.txt","r");
taille_fichier = SDL_RWseek(fichier,0,RW_SEEK_END);
printf("Le fichier \"exemple.txt\" contient %d caractères.",taille_fichier);
SDL_RWclose(fichier);
return 0;
}
La lecture
modifierEn théorie
modifierLa lecture d'un fichier consiste à récupérer le contenu de celui-ci afin de pouvoir l'utiliser dans un programme. Celle-ci se fait à l'aide d'une seule fonction : SDL_RWread. Voici son prototype :
size_t SDL_RWread(struct SDL_RWops* context, void* ptr, size_t size, size_t maxnum);
Cette fonction adopte un principe très simple. Elle permet de demander à lire une certaine partie du fichier dont on donne la taille. Lorsqu'on a lu cette partie du fichier, le curseur se déplace à la fin de celle-ci afin que, lorsqu'on souhaitera continuer la lecture, celle-ci reprenne là où nous l'avions laissé. On peut comparer son fonctionnement avec la lecture (réelle) d'un livre. Lorsqu'on ne souhaite pas lire le livre d'une traite, on lit le début du livre puis on met un marque page afin de reprendre là où on en était. Si l'on a oublié quelque chose, on peut toujours revenir sur ses pas, ce qui est possible grâce à SDL_RWseek.
De plus, lorsqu'on lit un livre, celui-ci est découpé en chapitres, qui nous permettent de savoir quand nous arrêter. De même, la fonction SDL_RWread demande à diviser les données en petit paquets d'une taille définie. Nous allons voir son fonctionnement plus en détail.
Sur ce principe, on comprend facilement les arguments de la fonction :
- context représente le contexte de lecture/écriture;
- ptr représente le tableau qui va contenir les données que l'on souhaite lire;
- size représente la taille d'un paquet contenant les données lues (si on est en lecture d'un fichier texte, ce sera le nombre de caractère pour un paquet, en lecture binaire, ce sera le nombre de bits pour un paquet);
- maxnum correspond au nombre de paquets lus.
La lecture d'une image se fait grâce à la fonction SDL_LoadBMP_RW dont le prototype est :
SDL_Surface* SDL_LoadBMP_RW(SDL_RWops * src, int freesrc);
src correspond alors à la structure RWops associée à l'image BMP que l'on souhaite ouvrir, et freesrc permet de fermer automatiquement le fichier une fois lu lorsqu'il vaut 1. En fait, la fonction SDL_LoadBMP que nous avons rencontré dans le chapitre sur les images est une macro utilisant directement les RWops. Elle est strictement identique à la combinaison suivante :
#define SDL_LoadBMP(file) SDL_LoadBMP_RW( SDL_RWFromFile(file,"rb"),1 )
En pratique
modifierIl est possible que le fonctionnement de cette fonction reste encore quelque peu obscur pour vous. Pour bien comprendre, rien ne vaut la pratique! C'est pourquoi il est conseillé de s'entraîner à lire des fichiers, quels qu’ils soient. Prenons ici l'exemple d'un dictionnaire que l'on appellera "dictionnaire.txt" . Voici son contenu :
Coucou j'adore la SDL
Dans ce fichier, chaque ligne représente un mot du dictionnaire. Ici le but est de récupérer le contenu du dictionnaire sous la forme d'un tableau de chaînes de caractère. Attention, l'objectif est de se servir des fonctions de lecture de la SDL. Par conséquent, ne récupérez pas tout le fichier dans une seule chaîne pour ensuite l'analyser avec string.h, le TP n'aurait alors plus de sens puisque vous n'aurez presque pas utilisé SDL_RWops.h. Le mieux serait en fait que vous n'utilisiez pas string.h et que vous vous concentriez sur la lecture : les mots doivent être déplacés sans transition du fichier vers le tableau de chaînes de caractère. Cela vous sera probablement bénéfique, surtout si vous débutez.
#include <SDL2/SDL.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char** dictionnaire;//Le tableau qui va contenir les mots du dictionnaire
char caractere;
int taille_fichier = 0, nombre_de_mots = 0,i = 0, j=0, nombre_de_lettres = 0;
SDL_RWops* fichier = SDL_RWFromFile("dictionnaire.txt","r"); // On ouvre le fichier "dictionaire.txt".
taille_fichier = SDL_RWseek(fichier,0,RW_SEEK_END); // On récupère la taille du fichier.
SDL_RWseek(fichier,0,RW_SEEK_SET); // On repart au début.
// On commence par compter le nombre de mots dans le fichier.
while(i < taille_fichier)
{
SDL_RWread(fichier,&caractere,1,1);
if(caractere == '\n')
nombre_de_mots++;
i++;
}
dictionnaire = (char**)malloc(nombre_de_mots * sizeof(char*));//On alloue l'espace nécessaire à notre dictionnaire
//À présent, on remplit le dictionnaire
SDL_RWseek(fichier,0,RW_SEEK_SET); // On repart au début.
while(j < nombre_de_mots)
{
nombre_de_lettres = 0;
do
{
SDL_RWread(fichier,&caractere,1,1);
nombre_de_lettres++;
}while(caractere != '\n');
nombre_de_lettres--; //On enlève le retour à la ligne
dictionnaire[j] = (char*)malloc(nombre_de_lettres*sizeof(char)); // On alloue le nombre de lettres adapté.
SDL_RWseek(fichier, -nombre_de_lettres-1, RW_SEEK_CUR); // On place la curseur au début du mot afin de le lire à nouveau.
SDL_RWread(fichier, dictionnaire[j],1,nombre_de_lettres); // On lit le mot.
SDL_RWseek(fichier, 1, RW_SEEK_CUR); // On saute le retour à la ligne.
j++;
}
for(i = 0; i<nombre_de_mots;i++)// On affiche notre dictionnaire bien stocké dans notre tableau de chaînes de caractère.
{
printf("%s \n",dictionnaire[i]);
}
SDL_RWclose(fichier); // Ne pas oublier de fermer le fichier
return 0;
}
L'écriture
modifierL'écriture avec la librairie SDL_RWops.h est très semblable au système de lecture que nous vous avons présenté plus haut. Elle se base principalement sur la fonction SDL_RWwrite que nous allons vous présenter ici :
size_t SDL_RWwrite(struct SDL_RWops* context, const void* ptr, size_t size, size_t num);
À l'instar de SDL_RWread, cette fonction va écrire bloc par bloc à partir de l'emplacement du curseur que nous avons décrit au début de ce chapitre. Ainsi, SDL_RWwrite demande en argument :
- context : le contexte de lecture/écriture;
- ptr : le tableau de données que l'on souhaite envoyer vers un fichier;
- size : la taille d'un bloc en octets;
- num : le nombre de blocs que l'on souhaite écrire.
Comme pour la lecture, lorsque vous écrivez, le curseur se déplace à la fin de ce que vous venez d'écrire afin que la fois suivante vous écrivez à la suite de ce que vous avez écrire. Pour insérer du texte, vous pouvez toujours utiliser la fonction SDL_RWseek. Remarquez que si vous utilisez l'option a lors de la création du contexte, le curseur se trouve dès le début à la fin du fichier.
La fonction SDL_RWwrite adoptant le même principe que SDL_RWread, nous n'ajouterons pas ici d'exercice pratique. Cependant, rien ne vous empêche de vous entraîner à écrire dans un fichier afin de bien en saisir le fonctionnement.