hamigaki.png

前のページ 上に戻る ホーム 次のページ

チュートリアル

簡単な例
パディング
複雑な例

簡単な例

簡単な例として、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構造体を取り上げた。この構造体のメンバのうち、bfReserved1bfReserved2は常に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

前のページ 上に戻る ホーム 次のページ