ENTRANCE|INFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)|Developper's Nest(開発掲示板)|こころ宙
zlib は ZIP 形式での圧縮と解凍を行うためのライブラリです。アーカイバの
gzip や lha などと異なるのは、このライブラリ単体では何もできない、というところです。たとえば
gzip はプログラムとして gzip それ単体で独立して動作させることができるのですが、zlib
はライブラリであるため、他のプログラムからソースコードのレベルで呼び出されなければ
(リンクされなければ) 解凍、圧縮の操作を行うことができません。
というわけで、この zlib はプログラムのコードを書けるレベルの人以外には必要ありません。プログラムを書かない方々にとっては謎のファイルに過ぎませんので、あしからず。
zlib は GNU ライセンスで配布されているので、自由に使うことができます。詳しくは
GNU のドキュメントを読んでください。…という自分自身、きっちり読んでなかったりします。ちゃんと読まないと。(^^;
ちなみに、Windows では DLL として、UNIX では SharedObject としてビルドできるようなので、うまくすれば他の言語
(Pascal や VisualBasic など) からも参照できるはずです。もっとも、それぞれの言語用の宣言なりが必要になると思いますけれども。
基本的に、zlib は C のソースコードで配布されています。ということで、使うためにはコンパイラが必要になります。では、どんなコンパイラが必要なのか? といいいますと、メジャーなコンパイラならば何でも良いみたいです。Watcom
用の Makefile もありました。
わたしが使っているのは Borland C++ Builder のコンパイラですが、Borland
C++ と同じ扱いなので Borland C++ Compiler でも大丈夫でしょう。もちろん、Visual
C++、Microsoft C も大丈夫です。Cygwin 環境での gcc もちゃんと動くようです。UNIX
環境ならば何の問題もありません。
また、バイナリ形式 (主に Windows 用 DLL) でも配布されていますが、それらのパッケージにはインクルードファイルなどが含まれていないため、開発目的で使用することはできないでしょう。
何にしても、zlib を自分のプログラムに組み込もうと考える場合、ソースコードを持ってきておいた方がいいでしょう。
ソースコードなどの配布は zlib site で行われています。こちらから拾ってきてください。
ダウンロードしてきた zip ファイルを展開すると、ソースコード一式、そして各環境用の
Makefile がサブディレクトリに展開されます。ま た、contrib ディレクトリ下には
zlib が公式にサポートしていないデータやユーティリティコードがあります。これらの中には
Pentium 用に最適化されたコードや、C++Builder 用のプロジェクトファイルが含まれていて、何かと役に立つ…かもしれません。
さて、アーカイブを展開した後どうするのか? といいますと、環境によって異なってきます。
UNIX や Cygwin 環境では
$ configure; make test; make install
でいいのですが、DOS や非 Cygwin な Windows 環境では、そういうわけにもいきません。(そもそも
configure が走りません)
サブディレクトリ msdos や os2 の下にある Makefile を使えばビルドできるようです。うまくいかない場合は
Makefile をちょっと修正し、自分の環境に合わせてみてください。どの Makefile
を使えばよいかは、拡張子を見ることで判断できるでしょう。
なにやら、contrib の下に iostream というディレクトリがあり、この下にある zfstream.cpp を使えば C++ の標準ライブラリである iostream に準拠した動作をしてくれるようです。ちょっと興味あるので、余裕があればそれの調査も行ってみます。
zlib を使うには、まず、ヘッダファイルをインクルードしなければなりません。
zlib で使われるヘッダファイルは zlib.h
と、zconf.h
です。これら二つのヘッダファイルを適切な場所へコピーしておきましょう。UNIX
環境であれば make install したときにコピーされているはずです。Windows 系であれば、ホームディレクトリ
(もしくはそれに準ずるディレクトリ) にコピーし、インクルードパスにそのディレクトリを追加します。
面倒ならば、zlib を組み込もうとしているプロジェクトのディレクトリにコピーしてしまいましょう。
実際にインクルードするファイルは zlib.h だけです。
…それと、当然ですが、zlib.lib
なども必要となります。これもリンクできる場所に置いておきましょう。
例:
> copy zlib.h zconf.h c:\home\black\include
> bcc32 -Ic:\home\black\include ...
圧縮を行うには、まず z_stream
構造体を作るところから始めなければなりません。名前から想像できると思いますが、zlib
では圧縮や解凍をメモリに対するストリーム操作のように扱います。そのため、ファイルから順次読み込みながら圧縮したり、解凍したりすることができます。
さて、z_stream
構造体ですが、とりあえずは次のように設定しておけば大丈夫でしょう。
z_stream z; z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL;
これらのメンバ (zalloc
など) は、メモリの割り当てに使用する関数を指定するために使われます。これらは圧縮処理などを行うときにコールバック関数として働きます。特別な事を行うときは、ここに自分で定義した関数
(へのポインタ) を指定しますが、通常であれば zlib が標準で用意しているもので問題ありませんので、Z_NULL を指定しておきます。(Z_NULL は NULL と同じです)
次に、圧縮アルゴリズムの初期化を行います。
z.next_in = (圧縮するデータへのポインタ); z.avail_in = (圧縮するデータの長さ); z.next_out = (圧縮されたデータを格納する場所へのポインタ); z.avail_out = (格納場所のサイズ); deflateInit( &z, Z_DEFAULT_COMPRESSION );
z.avail_in
と z.avail_out
は、現時点で 有効なバッファの大きさです。たとえば、圧縮するデータが 10000 バイトあり、メモリ上に読み込まれている圧縮データのサイズが
5000 バイトだったならば、z.avail_in
に 5000 を設定します。同様に、z.avail_out
には格納場所として確保されたバッファのサイズを指定します。
圧縮アルゴリズムの初期化には deflateInit()
関数を使います。ふたつめの引数には、圧縮レベルを指定します。特に理由がない限り、Z_DEFAULT_COMPRESSION
を指定すればよいでしょう。他の圧縮レベルとしては、Z_NO_COMPRESSION や Z_BEST_SPEED、Z_BEST_COMPRESSION があります。それぞれ、名前の通りの圧縮レベルになっています。また、数値で直接に指定することもできます。その場合、gzip
などに引き渡す圧縮レベルと同じ数値を使います。(1 が速度重視で 9 が圧縮率重視)
この初期化関数は、処理が成功したならば定数 Z_OK
を返します。失敗したならば、エラーコードを返します。なお、z.next_in
や z.next_out
などの値は deflateInit()
関数を呼び出した後から設定しても大丈夫です。
初期化が完了したら、いよいよ圧縮処理の本体です。
これから圧縮処理の基本的な流れを説明しますが、ここでは処理を「入力部」と「出力部」の二つに分けて説明します。どちらも独立性が高いので、ある程度は切り分けて考えることができます。
入力部は、すべてのデータを処理し終えるまで、次の作業を繰り返します。
出力部では、圧縮された入力データに対して、次の作業を繰り返します。
この圧縮処理は、deflate()
関数で行います。
deflate( &z, Z_NO_FLUSH );
ここが少々ややこしいのですが、deflate()
関数の返す値はひとつだけではありません。Z_OK
とZ_STREAM_END
という「二つの」正常終了値が返 されます。Z_OK
は圧縮処理が行われたことを示しますが、すべてのデータを処理したとは限りません。一方、Z_STREAM_END
は圧縮処理が完全に終了したことを示します。これら以外の値が返された場合は、エラーです。
deflate()
関数の第二引数も重要です。ここでは Z_NO_FLUSH
が指定されていますが、このままでは圧縮は完了しません。圧縮を終わらせるためには、最後の圧縮処理において
Z_FINISH
を指定しなければならないのです。Z_FINISH
を指定することで、ライブラリ内に保留されていた入出力が処理され、すべてのデータに対する入出力が完了するのです。
この辺りは、ソースコードを見た方が早いでしょうね。
まず、圧縮操作に対するサンプルプログラム (z.c) を示します。
/* z.c: zlib を使った圧縮プログラム */ #include <zlib.h> #include <stdio.h> #include <io.h> #include <fcntl.h> #include <malloc.h> /* 今回はバッファサイズを固定します */ #define BUFFER_SIZE (1024) int main ( int argc, char* argv[] ) { Bytef *ibuffer, *obuffer; /* 基本的に Bytef 型は char 型と同じ */ uInt isize, osize; /* uInt 型は unsigned int 型のこと */ z_stream z; int result; /* 入力元と出力先はバイナリモードでないと大変なことに */ if( setmode( fileno(stdin), O_BINARY ) < 0 || setmode( fileno(stdout), O_BINARY ) < 0 ) { fprintf( stderr, "setmode() failed." ); return -1; } /* デフォルトを使うので Z_NULL にて初期化 */ z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL; /* 圧縮準備 */ result = deflateInit( &z, Z_DEFAULT_COMPRESSION ); if( result != Z_OK ) { fprintf( stderr, "zlib error: %d: %s\n", result, z.msg ); return -1; } /* 解放の手間が必要ないため今回はバッファをスタックに割り当てます */ ibuffer = alloca(BUFFER_SIZE); obuffer = alloca(BUFFER_SIZE); z.next_in = NULL; z.avail_in = 0; /* ここで読み込んでおいてもいい */ z.next_out = obuffer; z.avail_out = BUFFER_SIZE; /* 標準入力から読み込んで圧縮したデータを標準出力に吐いていく */ while( !feof(stdin) ) { if( z.avail_in == 0 ) { /* 次のデータを持ってこんかァ! */ z.next_in = ibuffer; z.avail_in = fread( ibuffer, sizeof(Bytef), BUFFER_SIZE, stdin ); } result = deflate( &z, Z_NO_FLUSH ); /* 圧縮操作 */ if( result != Z_OK && result != Z_STREAM_END ) { fprintf( stderr, "zlib error: %d: %s\n", result, z.msg ); deflateEnd( &z ); return -1; } if( z.avail_out == 0 ) { /* 書き出してバッファを空けないと次が入らないッス */ fwrite( obuffer, sizeof(Bytef), BUFFER_SIZE, stdout ); z.next_out = obuffer; z.avail_out = BUFFER_SIZE; } } /* 保留されている (zlib 内に留まっている) データをすべて吐き出す */ while( result != Z_STREAM_END ) { result = deflate( &z, Z_FINISH ); /* Z_FINISH を指定しているのに注目 */ if( result != Z_OK && result != Z_STREAM_END ) { fprintf( stderr, "zlib error: %d: %s\n", result, z.msg ); deflateEnd( &z ); return -1; } /* どんどん書き出せやゴルァ! */ fwrite( obuffer, sizeof(Bytef), BUFFER_SIZE - z.avail_out, stdout ); z.next_out = obuffer; z.avail_out = BUFFER_SIZE; } deflateEnd( &z ); /* これが無いとメモリリークしちゃう */ return 0; }
とりあえず、これで圧縮処理を行うことができます。
圧縮操作が完了したら、zlib が使用したメモリを解放するために deflateEnd()
関数を呼び出さなければなりません。「プロセスが消えたらメモリも消えるじゃーん」などと楽観的に考えるのはやめましょう(笑)。
あとは、先にも述べましたが、zlib を使うプログラムはきちんと zlib.lib
をリンクするように指定しなければなりません。リンクできない場合は、make
がうまくいっているか、zlib.lib
がライブラリ検索パス内にあるかどうか、といったところをチェックしてみて下さい。
参考として、わたしがこのプログラムをコンパイル、リンクしたときのコマンドラインを書いておきます。
$ cd ~/temp $ gcc -I /src/zlib-1.1.4 z.c /src/zlib-1.1.4/libz.a
ちゃんと make install
されていれば gcc -lz z.c でいけると思います。(サボりました)
次は、展開処理に関してです。基本的に、行うことは圧縮処理と同じです。…が、使用する関数に引き渡すパラメータが異なっていたりするので注意しなければなりません。どうせならば、完全に対応を取ってほしかったのですが…。こちらは先にソース (x.c) を書いてしまいます。
/* x.c: zlib を使った展開プログラム */ #include <zlib.h> #include <stdio.h> #include <io.h> #include <fcntl.h> #include <malloc.h> /* 今回はバッファサイズを固定します */ #define BUFFER_SIZE (1024) int main ( int argc, char* argv[] ) { Bytef *ibuffer, *obuffer; /* 基本的に Bytef 型は char 型と同じ */ uInt isize, osize; /* uInt 型は unsigned int 型のこと */ z_stream z; int result; /* 入力元と出力先はバイナリモードでないと大変なことに */ if( setmode( fileno(stdin), O_BINARY ) < 0 || setmode( fileno(stdout), O_BINARY ) < 0 ) { fprintf( stderr, "setmode() failed." ); return -1; } /* デフォルトを使うので Z_NULL にて初期化 */ z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL; /* 展開準備 */ result = inflateInit( &z ); if( result != Z_OK ) { fprintf( stderr, "zlib error: %d: %s\n", result, z.msg ); return -1; } /* 解放の手間が必要ないため今回はバッファをスタックに割り当てます */ ibuffer = alloca(BUFFER_SIZE); obuffer = alloca(BUFFER_SIZE); z.next_in = NULL; z.avail_in = 0; /* ここで読み込んでおいてもいい */ z.next_out = obuffer; z.avail_out = BUFFER_SIZE; /* 標準入力から読み込んで展開したデータを標準出力に吐いていく */ while( !feof(stdin) ) { if( z.avail_in == 0 ) { /* 次のデータを持ってこんかァ! */ z.next_in = ibuffer; z.avail_in = fread( ibuffer, sizeof(Bytef), BUFFER_SIZE, stdin ); } result = inflate( &z, Z_SYNC_FLUSH ); /* 圧縮操作 */ if( result != Z_OK && result != Z_STREAM_END ) { fprintf( stderr, "zlib error: %d: %s\n", result, z.msg ); inflateEnd( &z ); return -1; } if( z.avail_out == 0 ) { /* 書き出してバッファを空けないと次が入らないッス */ fwrite( obuffer, sizeof(Bytef), BUFFER_SIZE, stdout ); z.next_out = obuffer; z.avail_out = BUFFER_SIZE; } } /* 保留されている (zlib 内に留まっている) データをすべて吐き出す */ while( result != Z_STREAM_END ) { result = inflate( &z, Z_SYNC_FLUSH ); if( result != Z_OK && result != Z_STREAM_END ) { fprintf( stderr, "zlib error: %d: %s\n", result, z.msg ); inflateEnd( &z ); return -1; } /* どんどん書き出せやゴルァ! */ fwrite( obuffer, sizeof(Bytef), BUFFER_SIZE - z.avail_out, stdout ); z.next_out = obuffer; z.avail_out = BUFFER_SIZE; } inflateEnd( &z ); /* これが無いとメモリリークしちゃう */ return 0; }
ここでは Z_SYNC_FLUSH を inflate() 関数に引き渡しています。このように、複数回に分けて展開処理を行う場合は Z_SYNC_FLUSH を引き渡します。一回で展開する場合は Z_FINISH を使います。それ以外で Z_FINISH を使うと、エラーで関数が失敗します。
zlib の使い方に関しては、英語ですが、zlib.h
にも書かれています。困ったら、それを読んでみましょう。
(というか、読めるのでしたら、このページを読む必要はないです(笑))
前の章において、圧縮と展開の方法を説明しました。しかし、実はもっと手軽にこれらの処理を行う方法があります。ユーティリティ関数の
compress
や uncompress
を使う方法です。これらとファイルマッピングを組み合わせると、かなり便利なものになります。
なお、ファイルマッピングは環境によって使い方が変わってきます。ここでは
Linux を例にして説明していきます。Cygwin でも同じようにできますが、わたしの環境では、なぜか
compress
関数をリンクすることができませんでした。zlib のソースから直接リンクすれば大丈夫だと思うのですが…。
まずは圧縮プログラム (comp.c) の方から。
/* comp.c: zlib の compress 関数を使った圧縮プログラム */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/io.h> /* Win では io.h (sys が要らない) */ #include <zlib.h> int main ( int argc, char** argv ) { struct stat filestat; /* ファイルサイズを得るのに使う */ int fd; /* ファイルディスクリプタ (fopen を使う場合は fileno で得る) */ void* sourceptr; Bytef* destptr; uLongf destlen; /* 引数のファイルを圧縮する */ if( argc < 1 ) { fprintf( stderr, "No input file.\n" ); return -1; } /* compress はファイルディスクリプタを受け取るので open を使う */ fd = open( argv[1], O_RDONLY ); /* Win では O_BINARY を指定する必要あり */ if( fd < 0 ) { fprintf( stderr, "Cannot open %s.\n", argv[1] ); return -1; } /* ファイルサイズが必要なので fstat で調べる */ if( fstat( fd, &filestat ) == 0 ) { /* 読み込み専用でファイルマッピング */ sourceptr = mmap( NULL, filestat.st_size, PROT_READ, MAP_SHARED, fd, 0 ); if( sourceptr != MAP_FAILED ) { /* zlib.h によると元ファイルの 1.1 倍 + 12 バイトを用意すればいい */ destlen = filestat.st_size * 1.1 + 12; destptr = (Bytef*)(malloc(destlen)); /* ここで一気に圧縮! */ compress( destptr, &destlen, sourceptr, filestat.st_size ); /* 面倒くさいので標準出力に書き出す */ fwrite( destptr, destlen, 1, stdout ); /* 使ったものの後始末 */ free( destptr ); munmap( sourceptr, filestat.st_size ); } } close( fd ); return 0; }
引数として受け取ったファイルを圧縮し、標準出力に書き出します。バイナリが書き出されてくるので、何かのファイルにリダイレクトしてやらないとコンソールが危険なことになります(笑)。
「圧縮率を変えたい!」という場合は、compress2
関数を使います。これは compress
の引数の最後に圧縮レベルを追加することができます。たとえば、上の例を最大の圧縮率で走らせたい場合は
compress2( destptr, &destlen, sourceptr, filestat.st_size, Z_BEST_COMPRESSION );
のようにします。
さて、逆に展開する方ですが、実はほとんど変わりません。あまりにも変わらないので、ソースは割愛させていただきます。いや、だって、本当に
compress
周辺しか変わらないんですもの。
destlen = 1024 * 1024; /* 元サイズを記録していないので… */ uncompress( destptr, &destlen, sourceptr, filestat.st_size );
…これしか変わりません。destlen
を圧縮前のサイズ以上に設定し、compress
を uncompress
に変えるだけです。これだけで展開プログラムができあがります。先ほどのプログラムで圧縮したファイルを引数に指定すると、標準出力に展開されたデータが出力されてきます。
ここでは手抜きのために元データのサイズを保存しませんでしたが (おいおい)、本来であれば、元のデータサイズを圧縮されたデータの最初に付け加えておくことになるでしょう。もしくは、固定データを圧縮する場合であれば、展開後のサイズはわかっていますよね。
メモリに収まってしまう程度のものであれば、正統的な inflate
関数などを使うより、こちらの compress
関数などを使った方が楽でしょう。
以下の方に、ソースの不備などを指摘していただきました。どうも、ありがとうございます。
ENTRANCE|INFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)|Developper's Nest(開発掲示板)|こころ宙