zlib

ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙


  1. zlib とは?
    1. 準備
    2. コンパイル
    3. おまけ
  2. zlib を使う
    1. ソースを書く
  3. もっと詳しく
    1. お手軽圧縮とお気楽展開
  4. 更新履歴
  5. 謝辞

zlib とは?

 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 で使われるヘッダファイルは 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_NULLNULL と同じです)
 次に、圧縮アルゴリズムの初期化を行います。

z.next_in = (圧縮するデータへのポインタ);
z.avail_in = (圧縮するデータの長さ);

z.next_out = (圧縮されたデータを格納する場所へのポインタ);
z.avail_out = (格納場所のサイズ);

deflateInit( &z, Z_DEFAULT_COMPRESSION );

 z.avail_inz.avail_out は、現時点で 有効なバッファの大きさです。たとえば、圧縮するデータが 10000 バイトあり、メモリ上に読み込まれている圧縮データのサイズが 5000 バイトだったならば、z.avail_in に 5000 を設定します。同様に、z.avail_out には格納場所として確保されたバッファのサイズを指定します。
 圧縮アルゴリズムの初期化には deflateInit() 関数を使います。ふたつめの引数には、圧縮レベルを指定します。特に理由がない限り、Z_DEFAULT_COMPRESSION を指定すればよいでしょう。他の圧縮レベルとしては、Z_NO_COMPRESSIONZ_BEST_SPEEDZ_BEST_COMPRESSION があります。それぞれ、名前の通りの圧縮レベルになっています。また、数値で直接に指定することもできます。その場合、gzip などに引き渡す圧縮レベルと同じ数値を使います。(1 が速度重視で 9 が圧縮率重視)
 この初期化関数は、処理が成功したならば定数 Z_OK を返します。失敗したならば、エラーコードを返します。なお、z.next_inz.next_out などの値は deflateInit() 関数を呼び出した後から設定しても大丈夫です。

 初期化が完了したら、いよいよ圧縮処理の本体です。
 これから圧縮処理の基本的な流れを説明しますが、ここでは処理を「入力部」と「出力部」の二つに分けて説明します。どちらも独立性が高いので、ある程度は切り分けて考えることができます。
 入力部は、すべてのデータを処理し終えるまで、次の作業を繰り返します。

  1. 圧縮対象となるデータをメモリに格納
  2. 格納されたデータを圧縮

 出力部では、圧縮された入力データに対して、次の作業を繰り返します。

  1. 圧縮データをディスクなどに書き込む
  2. バッファを空けて次の圧縮データを入れられるようにする

 この圧縮処理は、deflate() 関数で行います。

deflate( &z, Z_NO_FLUSH );

ここが少々ややこしいのですが、deflate() 関数の返す値はひとつだけではありません。Z_OKZ_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_FLUSHinflate() 関数に引き渡しています。このように、複数回に分けて展開処理を行う場合は Z_SYNC_FLUSH を引き渡します。一回で展開する場合は Z_FINISH を使います。それ以外で Z_FINISH を使うと、エラーで関数が失敗します。

 zlib の使い方に関しては、英語ですが、zlib.h にも書かれています。困ったら、それを読んでみましょう。
(というか、読めるのでしたら、このページを読む必要はないです(笑))

もっと詳しく

お手軽圧縮とお気楽展開

 前の章において、圧縮と展開の方法を説明しました。しかし、実はもっと手軽にこれらの処理を行う方法があります。ユーティリティ関数の compressuncompress を使う方法です。これらとファイルマッピングを組み合わせると、かなり便利なものになります。
 なお、ファイルマッピングは環境によって使い方が変わってきます。ここでは 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 を圧縮前のサイズ以上に設定し、compressuncompress に変えるだけです。これだけで展開プログラムができあがります。先ほどのプログラムで圧縮したファイルを引数に指定すると、標準出力に展開されたデータが出力されてきます。
 ここでは手抜きのために元データのサイズを保存しませんでしたが (おいおい)、本来であれば、元のデータサイズを圧縮されたデータの最初に付け加えておくことになるでしょう。もしくは、固定データを圧縮する場合であれば、展開後のサイズはわかっていますよね。
 メモリに収まってしまう程度のものであれば、正統的な inflate 関数などを使うより、こちらの compress 関数などを使った方が楽でしょう。

更新履歴

2002/05/06
ソースを書き直し。コメントの追加、安全性の向上。展開用ソースの追加。
若干の本文修正と追加。
2002/08/17
新しいホームページのスタイルに対応。
2002/08/18
「もっと詳しく」に「お手軽圧縮とお気楽展開」を追加。

謝辞

 以下の方に、ソースの不備などを指摘していただきました。どうも、ありがとうございます。


ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙