samedi 3 mai 2008

Zlib et C++

Depuis longtemps j'avais tenté d'éviter ce moment, mais finalement je m'y suis mis: écrire une couche d'abstraction c++ au dessus de zlib. En suivant pas à pas la documentation, et en remplaçant les concepts C par des concepts C++ (principalement l'utilisation des streams), j'ai fini par obtenir un bout de code qui me semble fonctionner correctement, sans trop avoir à se casser la tête.

Ce qui m'y a poussé, c'est d'abord que j'avais besoin d'un bout de code comme cela pour le boulot. Je me servais de boost::iostream, mais soit je l'utilise mal, soit il reste des bugs dans cette bibliothèque de boost (ce qui n'est impossible, en voyant la mailing-list), mais Purify m'indiquait de nombreuses fuites mémoires dues à la compression. Le code étant trop complexe pour un fix rapide, et ne pouvant de toutes façons pas patcher boost à travers l'ensemble de la boite, je me suis mis à écrire ce wrapper minimaliste.

C'est quand même une certaine déception que ces nouvelles bibliothèques de Boost aient autant de problèmes. Leur wrapper zlib fuit et n'accepte de toutes façons pas les valeurs négatives pour le paramètre "window" permettant de supprimer l'en-tête et le hash final, le rendant incompatible avec le cryptokit ocaml. Et la bibliotèque d'encodage en base 64 a des soucis de fin d'encodage (les '=' qui doivent indiquer comment se finit le flux). Quand on voit le temps qu'il faut pour compiler tout ça...

Comme cadeau bonus, voilà mon code, que vous pouvez bien entendu utiliser comme bon vous semble. Bien entendu, je ne garantis rien, donc si en l'exécutant, vous coupez l'alimentation oxygène de l'IIS, il ne faudra vous en prendre qu'à vous même. Na!


/* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://sam.zoy.org/wtfpl/COPYING for more details. */

#include "zlib.h"
#include <string>
#include <istream>
#include <sstream>
#include <stdexcept>
#include <iostream>

namespace
{
const int chunk = 256 * 1024;
}

void compress(std::istream & input, std::ostream & output, int level = 8, int window = 15)
{
unsigned char in[chunk];
unsigned char out[chunk];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;

if(deflateInit2(&strm,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
window,
level,
Z_DEFAULT_STRATEGY) != Z_OK)
{
throw std::runtime_error("deflateInit");
}

int flush;

do
{
input.read(reinterpret_cast<char*>(in), chunk);
strm.avail_in = input.gcount();
flush = (input.eof() || input.fail()) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
do
{
strm.avail_out = chunk;
strm.next_out = out;
deflate(&strm, flush);
output.write(reinterpret_cast<char*>(out), chunk - strm.avail_out);

}
while (strm.avail_out == 0);
}
while(flush != Z_FINISH);

deflateEnd(&strm);
}

void decompress(std::istream & input, std::ostream & output, int window = 15)
{
unsigned char in[chunk];
unsigned char out[chunk];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if(inflateInit2(&strm,
window))
{
throw std::runtime_error("inflateInit");
}

int ret;
do
{
input.read(reinterpret_cast<char*>(in), chunk);
strm.avail_in = input.gcount();
if(strm.avail_in == 0)
{
inflateEnd(&strm);
throw std::runtime_error("unexpected end of stream");
}
strm.next_in = in;
do
{
strm.avail_out = chunk;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
if(ret == Z_STREAM_ERROR ||
ret == Z_NEED_DICT ||
ret == Z_DATA_ERROR ||
ret == Z_MEM_ERROR)
{
inflateEnd(&strm);
throw std::runtime_error("inflate");
}
output.write(reinterpret_cast<char*>(out), chunk - strm.avail_out);

}
while(strm.avail_out == 0);
}
while(ret != Z_STREAM_END);
inflateEnd(&strm);
}


int main()
{
std::istringstream input("aaa");
std::ostringstream output;
compress(input, output);
std::cout << output.str() << std::endl;

std::istringstream input2(output.str());
std::ostringstream output2;
decompress(input2, output2);
std::cout << output2.str() << std::endl;

return 0;
}

Aucun commentaire: