C++を使用して、ファイルをgzip形式に圧縮する、または、gzip形式のファイルを展開する方法を説明します。
開発環境 | Visual Studio 2022 |
---|---|
言語 | C++ |
ライブラリ | zlib 1.3 gzip-hpp 1.0.0 |
動作確認 | Windows 11 Windows 10 Windows 7 |
Ubuntu (g++) FreeBSD (clang++) |
※UNIX系OSは、付属のzlibにてgzip-hppを確認しました。
Windows環境にzlibを導入する
zlibとは、C++にてgzipを扱うためのライブラリです。UNIX系OSには、標準で入っています。それは、「ls /usr/include/zlib.h」や「man zlib」と入力することで確認できます。
Windows SDKには含まれていないので、別途導入する必要があります。以下、zlibをVisual Studio 2022にてビルドする手順です。
(1)
https://zlib.net/からソース(zlib13.zip)をダウンロードして、展開します。
(2)
ターミナルより、[Developer Command Prompt for VS 2022]を起動します(または、[スタート]-[すべてのアプリ]-[Visual Studio 2022]-[Developer Command Prompt for VS 2022])。仮に、ソースの展開先を「C:\temp\zlib13\zlib-1.3」、生成されるライブラリの出力先を「C:\temp\zlib13\zlib」とする場合、以下のコマンドを実行します。その際、-Aオプションは、組み込み先のソリューションプラットフォームに合わせます。x86の場合、「Win32」を指定します。
> cd C:\temp\zlib13\zlib-1.3
> mkdir build
> cd build
> cmake .. -G "Visual Studio 17 2022" -A x64 -D CMAKE_INSTALL_PREFIX:PATH="C:\temp\zlib13\zlib"
> cmake --build . --config Release --target install
(3)
組み込み先のソリューションのプロジェクトフォルダーに、出力先の以下のファイルをコピーします。ここで、*.libファイルは名前変更可、*.dllファイルは名前変更不可です。
ファイル名 | 備考 |
---|---|
zconf.h | |
zlib.h | |
zlibstatic.lib | スタティックリンクの場合、ビルド時に必要 |
zlib.lib | ダイナミックリンクの場合、ビルド時に必要 |
zlib.dll | ダイナミックリンクの場合、実行時に必要。 アプリケーションと同じ、または、パスが通ったフォルダーに配置 |
(4)
プロジェクトのプロパティにて、[C/C++]-[全般]-[追加のインクルードディレクトリ]に、「$(ProjectDir)」が存在しない場合は追加します。また、[リンカー]-[入力]-[追加の依存ファイル]に、スタティックリンクの場合は「zlibstatic.lib」、ダイナミックリンクの場合は「zlib.lib」を追加します。これで、ソースに「#include <zlib.h>」を記述することで、zlibが使用可能となります。
gzip-hppを使う
ここからは、zlib導入済みのWindows、UNIX系OS共通の話になります。zlibを直接呼び出してgzipを扱う方法は、多くのウェブページにて解説されていますが、難しいし面倒くさいです。そこで、zlibのラッパーである「gzip-hpp」(https://github.com/mapbox/gzip-hpp)を使用すると簡単になります。
使用方法は、gzipフォルダーをプロジェクトフォルダーにコピーして、includeするだけです。ヘッダーファイルのみで動作します。
#include <gzip/compress.hpp>
#include <gzip/config.hpp>
#include <gzip/decompress.hpp>
#include <gzip/utils.hpp>
#include <gzip/version.hpp>
サンプルソースを作ってみました。圧縮アプリケーションの例です。圧縮前データの先頭ポインターとサイズをgzip::compressに渡すと、圧縮後のstd::stringが戻ってきます。
#include <fstream>
#include <filesystem>
#include <gzip/compress.hpp>
using namespace std;
int main(int argc, char *argv[])
{
// 最初の引数に、圧縮するファイルを指定
if (argc < 2)
{
return 1;
}
size_t size = filesystem::file_size(argv[1]);
char *pointer = new char[size];
// 圧縮するファイルの読み込み
ifstream ifs(argv[1], ios_base::binary);
ifs.read(pointer, size);
ifs.close();
// 圧縮
string compressed_data = gzip::compress(pointer, size);
delete[] pointer;
// gzipファイルの出力
// ファイル名は、入力ファイルに拡張子.gzを付加する。
ofstream ofs(argv[1] + string(".gz"), ios_base::binary);
ofs << compressed_data;
ofs.close();
}
展開アプリケーションの例です。最初に、gzip::is_compressedにてgzip形式かどうかをチェックします。これは先頭2byteが0x1F 0x8Bかどうかを見ています。次に、gzipデータの先頭ポインターとサイズをgzip::decompressに渡すと、展開後のstd::stringが戻ってきます。
#include <fstream>
#include <filesystem>
#include <gzip/decompress.hpp>
#include <gzip/utils.hpp>
using namespace std;
int main(int argc, char *argv[])
{
// 最初の引数に、gzipファイルを指定
if (argc < 2)
{
return 1;
}
size_t size = filesystem::file_size(argv[1]);
char *pointer = new char[size];
// gzipファイルの読み込み
ifstream ifs(argv[1], ios_base::binary);
ifs.read(pointer, size);
ifs.close();
// gzip形式のチェック
if (!gzip::is_compressed(pointer, size))
{
return 1;
}
// 展開
string decompressed_data = gzip::decompress(pointer, size);
delete[] pointer;
// 展開したファイルの出力
// ファイル名は、入力ファイルから拡張子.gzを削除する。
// 拡張子.gz以外の場合は、同じファイル名で上書きする。
filesystem::path pa = argv[1];
if (pa.extension() == ".gz")
{
pa.replace_extension();
}
ofstream ofs(pa, ios_base::binary);
ofs << decompressed_data;
ofs.close();
}
これらのサンプルソースは、ファイルサイズと拡張子の処理に<filesystem>を使用しているので、標準C++17(以降)でビルドしてください。Visual Studio 2022の場合、プロジェクトのプロパティにて、[全般]-[C++言語標準]に「ISO C++17標準(/std:c++17)」以降を選択します。
UNIX系OSは、下記の例を参考にしてください。(サンプルソースをtest.cpp、実行ファイルをtestとする場合)
# g++の例
> g++ -std=c++17 -Wall -I. test.cpp -lz -o test
# clang++の例
> clang++ -std=c++17 -Wall -I. test.cpp -lz -o test
gzipファイルフォーマットの公式仕様は、RFC1952(https://www.ietf.org/rfc/rfc1952.txt)です。これを読むと、gzipファイルは圧縮前のファイル名とタイムスタンプを格納することが可能ですが、必須ではありません。gzip-hppには、ファイル名やタイムスタンプのインターフェースが無いので、設定されないことになります。
UbuntuやFreeBSDに入っているgzip/gunzipコマンドの動きを見ると、圧縮時にファイル名とタイムスタンプを格納していますが、展開時は、それを参照していないようです。いずれにしても、いろんなパターンで試しましたが、サンプルソースとgzip/gunzipコマンドの相互運用性には問題ありませんでした。
最後に、RFC1952とzlibの実装(zlib-1.3\zutil.h)にて、異なる点を見つけました。それは、gzipファイルの先頭から10byte目のOS/ファイルシステム種別です。
値 | RFC1952 | zlib |
---|---|---|
0x00 | FAT | ← |
0x01 | Amiga | ← |
0x02 | VMS | ← |
0x03 | Unix | ←(他に該当しないOSは全てUNIX) |
0x04 | VM/CMS | ← |
0x05 | Atari TOS | ← |
0x06 | HPFS | ← |
0x07 | Macintosh | ← |
0x08 | Z-System | ← |
0x09 | CP/M | |
0x0A | TOPS-20 | Windows |
0x0B | NTFS | 0x04,0x08と同じシリーズのIBMの何か |
0x0C | QDOS | |
0x0D | Acorn RISCOS | ← |
0x0E | ||
0x0F | ||
0x10 | BeOS | |
0x11 | ||
0x12 | OS/400 | |
0x13 | 0x07以外のAppleの何か | |
0xFF | unknown |
このため、Windowsにて圧縮のサンプルソースを動かすと、0x0Bではなく、0x0Aが設定されます。