Programmer avec zlib en C

Creative Commons License
Cette création est mise à disposition sous un contrat Creative Commons.

L'article original se trouve sur http://ymettier.free.fr/articles_lmag/.

Article publié dans le numéro 22 (novembre 2000) de GNU/Linux France Magazine


Table des matières

1. Introduction
2. Le principe
3. Compilation du programme
4. Les fonctions principales de la zlib
5. Conversion d'un programme utilisant fopen et cie
6. Astuces avec les taux de compression.
7. Zlib sans les fichiers
8. Conclusion
9. Ressources

Résumé

Cet article se veut un point de départ pour programmer avec la librairie zlib. C'est l'article dont j'aurais eu besoin lorsque j'ai voulu programmer avec la zlib. Alors cet article, je l'ai écrit, le voilà: il est destiné à ceux qui utilisent les instructions habituelles du langage C pour lire et écrire dans un fichier ,et qui veulent profiter des avantages de la compression.

1. Introduction

La zlib, c'est quoi ? C'est une librairie dont le rôle est de lire et d'écrire des fichiers compressés au format zip. Ainsi, plutôt que de faire un gzip sur un fichier avant de pouvoir le lire par votre programme, ou plutôt que de devoir exécuter gzip à l'intérieur de votre programme pour sauver un fichier de plus petite taille, pourquoi ne pas utiliser une librairie dont le rôle est effectivement la compression et la décompression des fichiers ?

2. Le principe

Le principe de zlib est relativement simple. Votre programme utilise des instructions read et write : il suffit de les remplacer par les instructions de la zlib. En d'autres termes, plutôt que d'utiliser les instructions fournies par la glibc pour lire et écrire dans un fichier, on utilise les fonctions de la zlib. Voici un exemple de lecture dans un fichier : Un extrait de programme avant modification :

int fh;
char buffer[BUFFER_MAX_SIZE];

fh = open(filename, O_RDONLY);
while(read(fh, buffer, BUFFER_MAX_SIZE))
     traitement_du_buffer(buffer);
close(fh);

Le même extrait de programme après modification :

gzFile gzfh = NULL;
char buffer[BUFFER_MAX_SIZE];

gzfh = gzopen(filename, "rb");
while(gzread(gzfh, buffer, BUFFER_MAX_SIZE))
     traitement_du_buffer(buffer);
gzclose(gzfh);

Et qu'est-ce qui a changé ? Le type du descripteur du fichier qui était int et qui devient gzFile, et les instructions d'entrées/sorties qui forcément appartiennent à la zlib. Plus le mode de lecture du fichier qui est spécifié à l'ouverture du fichier. Par contre, tous les autres arguments n'ont pas changé.

3. Compilation du programme

Avant de compiler, il faut s'assurer qu'il y a bien une ligne avec #include <zlib.h>. Sinon, attention aux warnings. Puis il suffit d'ajouter l'option -lz au compilateur : la librairie s'appelle libz.so. gcc main.c -lz -o monprog

4. Les fonctions principales de la zlib

L'ouverture de fichier se fait avec gzopen au lieu de open ou fopen. Le second argument garde son rôle mais change de type. Les auteurs de zlib ont préféré le mode d'ouverture utilisé par l'instruction fopen de la librairie stdio. Ainsi, on spécifie une chaîne de caractères dont le premier caractère est "r" pour la lecture et "w" pour l'écriture, le "b" que j'ai ajouté dans l'exemple plus haut est un "b" qui assure la compatibilité ANSI avec fopen. Et lors de l'écriture d'un fichier, on peut ajouter le taux de compression, qui est un chiffre entre 0 (compression=0; vitesse=MAX) et 9 (compression=MAX; vitesse=LENTE). Cela donnera par exemple "wb6" pour une écriture de fichier compressé avec le même taux de compression que gzip lorsqu'on ne lui spécifie pas un taux de compression.

L'écriture dans un fichier se fait avec gzwrite. Les arguments sont les mêmes que ceux de write (man 2 write)

La lecture dans un fichier se fait avec gzread. Ici aussi, les arguments sont les mêmes que ceux de read.

La fermeture du fichier se fait avec gzclose. Comme on pouvait s'y attendre, ce n'est pas le descripteur de fichier au format int qu'on fournit en argument, mais celui au format gzFile donné par gzopen. Il en résulte que si l'on n'a pas changé le nom du descripteur du fichier mais seulement son type lors d'une conversion de code vers la zlib, alors il n'y a rien a changer pour close ou fclose.

Les autres instructions, fprinf, fputs, fgets, fputc, fgetc, fflush, fseek, ftell, rewind et feof sont respectivement à remplacer par gzprintf, gzputs, gzgets, gzputc, gzgetc, gzflush, gzseek, gzrewind et gzeof. Les arguments sont les mêmes pour chaque instruction et son instruction correspondante dans la librairie zlib, à l'exception dont vous vous doutez : le descripteur de fichiers qui est de type gzFile.

Il reste une instruction qui peut être intéressante pour la lecture et l'écriture de fichiers, c'est-à-dire la conversion d'un descripteur de fichiers fourni par open vers un descripteur de fichier de type gzFile. Cette instruction est gzFile gzdopen(int fd, const char *mode), qui prend en argument le descripteur de fichiers d'open et le mode que l'on aurait fourni à gzopen. C'est le correspondant de fdopen.

5. Conversion d'un programme utilisant fopen et cie

Pour avoir un meilleur contrôle sur la conversion d'un programme utilisant fopen, fread, fwrite, fclose et les autres, il faut d'abord changer les quatre instructions précédentes, ce qui nécessite un peu plus d'attention que si l'on avait open, read, write et close car les arguments changent pour fread et fwrite. fread(ptr,a,b,fh) devient gzread(gzfh, ptr, a*b) et fwrite(ptr, a, b, fh) devient gzwrite(fh, ptr, a*b).

6. Astuces avec les taux de compression.

Comment faire pour chercher un bug avec un éditeur de fichier ? Le fichier est compressé, donc il est encore plus difficile de voir ce qu'il se passe lors de l'écriture du fichier. Sauf que si le taux de compression est 0, il n'y a aucune compression, et le fichier est sauvé "en clair". Il suffit donc de modifier temporairement le mode dans l'instruction gzopen, et le tour est joué!

Peut-on encore lire un fichier non compressé quand on utilise la librairie zlib ? Oui! Un fichier non compressé peut aussi être considéré comme un fichier compressé avec un taux de compression nul!

7. Zlib sans les fichiers

Si l'on veut juste compresser une zone de mémoire, il existe trois instructions pour cela :

int compress (Bytef *dest, uLongf *longueur_dest, const Bytef *source, uLong longueur_source);
int compress2 (Bytef *dest, uLongf *longueur_dest, const Bytef *source, uLong longueur_source, int niveau);

Ces deux instructions compressent un espace mémoire qui se trouve à l'adresse pointée par source et de longueur source_dest. Le résultat se trouve à l'adresse pointée par dest de taille longueur_dest. Attention, l'expression suivante doit toujours être vérifiée :

longueur_dest >= (longueur_source + longueur_source * 10% + 12)

C'est-à-dire

longueur_dest >= (longueur_source * 11/10 + 12)

L'argument niveau est tout simplement le taux de compression, un int compris entre 0 et 9 inclus.

int uncompress (Bytef *dest, uLongf *longueur_dest, const Bytef *source, uLong longueur_source);

Cette instruction effectue le travail inverse des deux instructions précédentes. La source de taille longueur_source contient les données en mémoire à décompresser. Par contre, la dest qui va accueillir le résultat doit avoir une longueur longueur_dest suffisante pour la totalité de l'espace mémoire décompressé.

Les trois fonctions renvoient Z_OK si tout s'est bien passé, Z_MEM_ERROR si la mémoire était insuffisante, Z_BUF_ERROR si le buffer de sortie était trop petit, ou Z_DATA_ERROR si les données en entrée étaient corrompues.

8. Conclusion

Nous avons vu les principales fonctions de la librairie zlib et leur simplicité d'utilisation lorsqu'on sait déjà lire et écrire dans un fichier en C. Il ne vous reste maintenant plus qu'à modifier ces instructions d'entrée/sorties et à utiliser celles de la zlib. N'oubliez pas de laisser à l'utilisateur de votre programme le choix du niveau de compression : il connaît mieux que vous la taille de son disque dur et le temps à accorder à la sauvegarde de ses fichiers.

9. Ressources

création est mise à disposition sous un contrat Creative Commons