自作のアプリケーションでデータを圧縮解凍したいと思うことがたまにある.zlibを使用すると簡単にzip圧縮と解凍ができるようなので試してみた.
zlib.h
圧縮
エラーメッセージの取得
解凍
コンパイルと実行
zlib.h
zlibにはいろいろな関数がたくさんあって,その詳細はzipの仕様がわかっていないとなかなかつらそうだったりもする. で,zlib.hを眺めていると,詳細な操作を行う関数の他に,簡単に使用したい人のための関数も用意されているようなので,今回はそれらを使用してみる. zlibの各関数にはmanがないようだが,zlib.hには詳細なドキュメントがコメントされているので,困ったらそれを読むとよい.
圧縮
データをzip形式圧縮してファイル出力するサンプルを以下に示す.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#define GZ_MODE "wb6f"
int
main(int argc, char *argv[])
{
if (argc != 4)
{
fprintf(stderr, "Usage : %s filename string count\n", argv[0]);
exit(1);
}
// 出力回数
int count = atoi(argv[3]);
// 出力文字合計
unsigned long sum = 0L;
// gz出力ファイルをオープンする.
gzFile outFile = gzopen(argv[1], GZ_MODE);
if (outFile == NULL)
{
fprintf(stderr, "failed to gzopen\n");
exit(1);
}
int i;
for (i = 0; i < count; i++)
{
// データをzip形式で圧縮して出力する.
int writeLen = gzwrite(outFile, argv[2], strlen(argv[2]));
if (writeLen != strlen(argv[2]))
{
fprintf(stderr, "failed to gzwrite\n");
exit(1);
}
sum += writeLen;
}
printf("wrote %lu\n", sum);
// gzファイルのクローズ
int ret;
if ((ret = gzclose(outFile)) != Z_OK)
{
// gzcloseに失敗した場合,ファイルは閉じられるのでgzerrorできない
fprintf(stderr, "gzclose failed.\n");
exit(1);
}
return 0;
}
簡易APIを使用すると,通常のファイル出力と同様,オープン,書き出し,クローズの三段階の手順となる.
ファイルのオープン
ファイルのオープンはfopenに似ている.
ZEXTERN gzFile ZEXPORT
gzopen OF((const char *path, const char *mode));
pathが出力ファイルパス,modeはfopenのモード+圧縮モードである. fopenのモードは”rb”か”wb”で,圧縮モードは
圧縮レベル [圧縮方式]
である. 圧縮レベルは
0 : 圧縮なし
1 : 処理速度重視
9 : 圧縮効率重視
で,圧縮方式は
f : フィルタ p
h : ハフマン符号化のみ
R : ランレングス
のようだ. gzopenで返されるgzFileオブジェクトが,今後の各zlib関数に渡すハンドラとなる. エラー時はgzopenがNULLとなる.
圧縮と書き出し
データの圧縮と書き出しは一つの関数呼び出しで行える.
ZEXTERN int ZEXPORT
gzwrite OF((gzFile file,
voidpc buf, unsigned len));
第一引数はgzopenの返りを使用する. あとは書き出しデータ(圧縮前)が格納されたバッファとその長さである. 返り値は書き出されたデータの圧縮前の長さが返される. 返り値が0の場合はエラーらしい.
クローズ
クローズはfcloseと同様,簡単. 第一引数はgzopenの返りを使用する.
ZEXTERN int ZEXPORT
gzclose OF((gzFile file));
返り値はエラーナンバーらしく,たぶんZ_OK以外はエラー. エラー時でもファイルはクローズされるらしい.
エラーメッセージの取得
zlibでは,関数呼び出しでエラー番号が返された場合,そのエラー番号からエラーメッセージを取得できる. つまり,標準ライブラリでstrerrorがあるように,zlibではgzerrorがある.
ZEXTERN const char * ZEXPORT
gzerror OF((gzFile file, int *errnum));
返り値がerrnumに対応するエラーメッセージである. 第一引数はgzopenの返りであり,クローズされたものは使用できない. だから,gzopenやgzclose失敗時には使用できない. 第二引数は入出力項目で関数から返されたエラー番号を設定する. zlibではなく,ファイルシステムによるエラーの場合は,errnumにZ_ERRNOと設定される. この場合は,errnoが設定されているので,strerrorを使用して標準ライブラリからエラーメッセージを取得する.
解凍
zip形式ファイルを解凍して読み出すサンプルを以下に示す.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#define GZ_MODE "rb6f"
#define BUF_SIZE (512)
int
main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage : %s filename\n", argv[0]);
exit(1);
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
perror("failed to open");
exit(1);
}
gzFile inFile = gzdopen(fd, GZ_MODE);
if (inFile == NULL)
{
fprintf(stderr, "failed to gzdopen.\n");
exit(1);
}
int ret;
unsigned long sum = 0;
char buf[BUF_SIZE];
while((ret = gzread(inFile, buf, sizeof(buf))) != 0
&& ret != -1)
{
sum += ret;
}
if(ret == -1)
{
// gzerrorでエラーメッセージを取得する
const char *msg = gzerror(inFile, &ret);
if (ret == Z_ERRNO)
{
msg = strerror(ret);
}
fprintf(stderr, "gzread failed. %s\n", msg);
exit(1);
}
else
{
printf("read %lu\n", sum);
}
if ((ret = gzclose(inFile)) != Z_OK)
{
// gzcloseに失敗した場合,ファイルは閉じられるのでgzerrorできない
fprintf(stderr, "gzclose failed.\n");
exit(1);
}
return 0;
}
簡易APIを使用すると,やはり通常のファイル出力と同様,オープン,読み込み,クローズの三段階の手順となる. サンプルのクローズは圧縮時と同様である. オープンは圧縮時とは別の方法としてファイルディスクリプタを指定する方法を使用している. もちろん圧縮時と同様の方法も使用できるし,ファイルディスクリプタを使用する方法を圧縮時に使用することもできる.
ファイルディスクリプタ指定のオープン
gzopenはfopenに似ていたが,gzdopenはfdopenに似ている.
ZEXTERN gzFile ZEXPORT
gzdopen OF((int fd, const char *mode));
ファイルディスクリプタを引数にもつので,圧縮データの読み書き対象がファイルだけでなく,ネットワークストリームやパイプであってもzlibが使用できる. 当然ファイルディスクリプタは標準関数を使用してオープンしておく必要がある. 後でgzcloseした場合,ファイルディスクリプタもクローズされる. 返り値はgzopenと同様.
解凍と読み込み
解凍と読み込みは,一つの関数呼び出しでできる.
ZEXTERN int ZEXPORT
gzread OF((gzFile file, voidp buf, unsigned len));
fileはgzopenやgzopenの返り値,bufは読み込み用バッファ,lenは読み込む解凍後のデータサイズ, 返り値は読み込んだデータの解凍後のバイト数である. ファイルの終りに達した場合は0,エラー時は-1が返る.
コンパイルと実行
コンパイルする際は,リンカオプションを設定する.
LDFLAGS=-lz
では,圧縮,解凍と続けて実行してみる.
# 圧縮
$ ./mygzwrite outfile.gz FIREPROJECT 5
wrote 55
# 出力サイズは55文字に対して34バイトなので,圧縮されているようだ
$ ls -l outfile.gz
-rw-r--r-- 1 matsu matsu 34 2007-04-21 13:00 outfile.gz
# 解凍. 正しく55文字に解凍されている.
$ ./mygzread outfile.gz
read 55
# ツールで出力を解凍,表示
$ gunzip outfile.gz
$ cat outfile
FIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECTFIREPROJECT
TOPページ
不安火
Feature
Automake
AXIS
BASHスクリプト
Common Lisp
C++
C言語
GDB
GTK
Hibernate
JFreeChart
JNI
MPEG
NMS
Perl
PostgreSQL
Restlet
Spring
Struts
Taglib
XML
XUL
埋火
PC Cluster
ベンチマーク調査
ギガビットイーサネットの調査
MPIで並列プログラミング
Software
hpl_filter
SiteGenerator
GCMon
HKB
Software(En)
SiteGenerator