![]() |
簡単な例として、Microsoft Windowsで用いられるビットマップファイルのヘッダ、BITMAPFILEHEADER
構造体のバイナリ出力を考える。
typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER;
ここで、WORD
は16ビット符号なし整数を、DWORD
は32ビット符号なし整数を意味している。
この構造体の意図している利用法は次のようなものである。
void write_header(std::FILE* fp, ::DWORD size, ::DWORD offset) { ::BITMAPFILEHEADER header; std::memset(&header, 0, sizeof(header)); header.bfType = static_cast< ::WORD>('B') | (static_cast< ::WORD>('M') << 8); header.bfSize = size; header.bfOffBits = offset; std::fwrite(&header, sizeof(header), 1, fp); }
この方法には次のような問題がある。
エンディアン
この構造体の各フィールドは2バイトまたは4バイトであり、出力の際にはバイト順を考慮する必要がある。
ビットマップファイルの想定するエンディアンはリトルエンディアンであり、ビッグエンディアンの環境では正しいバイト順で出力されない。
構造体のパディング
C/C++標準は、ハードウェア上の制約や処理効率の向上のため、構造体の各フィールドの間にパディングを挿入することを許している。
ビットマップファイルでは、フィールド間にパディングがあってはならないため、パディングが挿入される環境では意図した出力は得られない。実際、Microsoftの提供するヘッダファイルでは、独自のpragma指令を用いてパディングの挿入を防ぐようになっている。
これらの問題は、BITMAPFILEHEADER
のメモリイメージをファイルに書き出していることに原因がある。ここで重要なのはファイルに書き出されるバイト列であって、メモリ上のレイアウトではない。正しい出力を得るためには、出力時のエンディアンやフィールドの配置を指示する必要がある。Hamigaki.Binaryでは、構造体に対し、テンプレートstruct_traits
の特殊化を用意することでこれを行う。
#include <hamigaki/binary/struct_traits.hpp> #include <boost/mpl/list.hpp> namespace hamigaki { template<> struct struct_traits< ::BITMAPFILEHEADER> { private: typedef ::BITMAPFILEHEADER self; public: typedef boost::mpl::list< member<self, ::WORD, &self::bfType, little>, member<self, ::DWORD, &self::bfSize, little>, member<self, ::WORD, &self::bfReserved1, little>, member<self, ::WORD, &self::bfReserved2, little>, member<self, ::DWORD, &self::bfOffBits, little> > members; }; } // namespace hamigaki
特殊化されたstruct_traits
では、members
という名前の型を用意しなければならない。members
はBoost.MPLのSequence
の形を取る。通常は、boost::mpl::list
を使う。
members
の要素は、member
である。これが構造体のフィールドの出力方法を決定する。member
のテンプレート引数は、順に「構造体の型」、「フィールドの型」、「フィールドのメンバポインタ」、「エンディアン」である。
また、members
中の要素順は、出力の際の順序として用いられる。
これを使ってバイナリ出力を行うには、次のようにする。
#include <hamigaki/binary/binary_io.hpp> void write_header(std::FILE* fp, ::DWORD size, ::DWORD offset) { ::BITMAPFILEHEADER header; std::memset(&header, 0, sizeof(header)); header.bfType = static_cast< ::WORD>('B') | (static_cast< ::WORD>('M') << 8); header.bfSize = size; header.bfOffBits = offset; char buffer[hamigaki::struct_size< ::BITMAPFILEHEADER>::value]; hamigaki::binary_write(buffer, header); std::fwrite(buffer, 1, sizeof(buffer), fp); }
関数binary_write()
は、struct_traits
の指示に従い、第一引数のバッファに第二引数の構造体オブジェクトをバイナリ出力する。バッファに必要なサイズは、メタ関数struct_size
で調べることができる。
今度のコードは、実行環境のエンディアンや構造体のパディングに影響されないため、常に正しい出力を得ることができる。
同様に、関数binary_read()
を用いることでバイナリ入力を行うこともできる。
#include <hamigaki/binary/binary_io.hpp> ::BITMAPFILEHEADER read_header(std::FILE* fp) { char buffer[hamigaki::struct_size< ::BITMAPFILEHEADER>::value]; std::fread(buffer, 1, sizeof(buffer), fp); ::BITMAPFILEHEADER header; hamigaki::binary_read(buffer, header); return header; }
前の例では、BITMAPFILEHEADER
構造体を取り上げた。この構造体のメンバのうち、bfReserved1
とbfReserved2
は常に0を設定することになっている。これらのプログラム中で必要ないメンバを削った新しい構造体bitmap_file_header
を考える。
struct bitmap_file_header { boost::uint16_t type; boost::uint32_t size; boost::uint32_t offset; };
struct_traits
は次のように変更する。
#include <hamigaki/binary/struct_traits.hpp> #include <boost/mpl/list.hpp> namespace hamigaki { template<> struct struct_traits< ::bitmap_file_header> { private: typedef ::bitmap_file_header self; public: typedef boost::mpl::list< member<self, boost::uint16_t, &self::type, little>, member<self, boost::uint32_t, &self::size, little>, padding<2>, padding<2>, member<self, boost::uint32_t, &self::offset, little> > members; }; } // namespace hamigaki
ここで注目すべきはpadding
である。padding
は、対応するメンバ変数が存在しない場合に、member
の代わりとして使用できる。テンプレート引数はパディングのバイト数である。binary_write()
で書き出した際は、そのバイト数だけ0が出力される。
ここでは、2バイトのパディングを2個使用しているが、当然4バイトのパディングを1個使用しても結果は同じである。
今度は少し複雑な例を取り上げる。
typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; namespace hamigaki { template<> struct struct_traits< ::RGBQUAD> { private: typedef ::RGBQUAD self; public: typedef boost::mpl::list< member<self, BYTE, &self::rgbBlue>, member<self, BYTE, &self::rgbGreen>, member<self, BYTE, &self::rgbRed>, member<self, BYTE, &self::rgbReserved> > members; }; template<> struct struct_traits< ::BITMAPINFOHEADER> { private: typedef ::BITMAPINFOHEADER self; public: typedef boost::mpl::list< member<self, DWORD, &self::biSize, little>, member<self, LONG, &self::biWidth, little>, member<self, LONG, &self::biHeight, little>, member<self, WORD, &self::biPlanes, little>, member<self, WORD, &self::biBitCount, little>, member<self, DWORD, &self::biCompression, little>, member<self, DWORD, &self::biSizeImage, little>, member<self, LONG, &self::biXPelsPerMeter, little>, member<self, LONG, &self::biYPelsPerMeter, little>, member<self, DWORD, &self::biClrUsed, little>, member<self, DWORD, &self::biClrImportant, little> > members; }; } // namespace hamigaki
ここで、BYTE
は8ビット符号なし整数を、LONG
は32ビット符号付き整数を意味している。
上記のような構造体を組み合わせて構造体bitmap_info_256
を定義する。
struct bitmap_info_256 { BITMAPINFOHEADER header; RGBQUAD colors[256]; };
これに対するstruct_traits
は、次のように書くことができる。
namespace hamigaki { template<> struct struct_traits< ::bitmap_info_256> { private: typedef ::bitmap_info_256 self; public: typedef boost::mpl::list< member<self, BITMAPINFOHEADER, &self::header>, member<self, RGBQUAD[256], &self::colors> > members; }; } // namespace hamigaki
member
に指定するフィールドの型には、整数値以外にも構造体や配列も指定できる。binary_write()
で出力する際には、入れ子となった構造体や配列の各要素に対して再帰的にbinary_write()
が呼び出される。
製作著作 © 2006, 2007 Takeshi Mouri |