構造体のポータブルなバイナリ読み書き
MPLの力技で構造体のバイナリ読み書きをやってみました。
<hamigaki/binary_io.hpp>
<hamigaki/struct_traits.hpp>
使い方はこんな感じです。
#include <hamigaki/binary_io.hpp> #include <boost/mpl/list.hpp> #include <boost/cstdint.hpp> #include <iostream> #include <fstream> #include <cassert> #include <cstring> // LZH Level2ヘッダ // 構造体にパディングが入っても、バイナリとメンバの順が違ってもよい // ただし、個々のメンバのサイズは一致しないと駄目 struct level2_header { boost::uint16_t header_size; char signature[5]; boost::uint32_t compressed_size; boost::uint32_t file_size; boost::int32_t update_time; boost::uint8_t reserved; boost::uint8_t level; boost::uint16_t crc; char os_type; }; namespace hamigaki { // 構造体ごとに行う特殊化 template<> struct struct_traits<level2_header> { private: typedef level2_header self; static const hamigaki::endianness endian = hamigaki::little; public: // メンバの型、メンバポインタ、エンディアンのリスト typedef boost::mpl::list< hamigaki::member<self, boost::uint16_t, &self::header_size, endian>, hamigaki::member<self, char[5], &self::signature>, hamigaki::member<self, boost::uint32_t, &self::compressed_size, endian>, hamigaki::member<self, boost::uint32_t, &self::file_size, endian>, hamigaki::member<self, boost::int32_t, &self::update_time, endian>, hamigaki::member<self, boost::uint8_t, &self::reserved>, hamigaki::member<self, boost::uint8_t, &self::level>, hamigaki::member<self, boost::uint16_t, &self::crc, endian>, hamigaki::member<self, char, &self::os_type> > members; }; } // namespace hamigaki int main(int argc, char* argv[]) { if (argc != 2) return 1; // ファイルからchar配列に読み込み // struct_size<T>はTの外部バイナリサイズを返すメタ関数 char data[hamigaki::struct_size<level2_header>::type::value]; std::ifstream is(argv[1], std::ios_base::binary); is.read(data, sizeof(data)); // char配列から構造体をバイナリ読み込み level2_header header; hamigaki::binary_read(data, header); // 読み出したデータを表示 std::cout << "header_size = " << header.header_size << std::endl; (std::cout << "signature = ").write(header.signature, 5) << std::endl; // char配列へ構造体をバイナリ書き出し char data2[hamigaki::struct_size<level2_header>::type::value]; hamigaki::binary_write(data2, header); // 結果比較 assert(std::memcmp(data, data2, sizeof(data)) == 0); }
必要なことはstruct_traitsの特殊化だけです。
メンバ変数の情報をmpl::listで持っていて、これを頼りにメンバ変数を反復処理します。
このリストは反復に必要なので手書きしないと駄目です。
MPLシーケンス中の各型に対して実行時の処理を行いたい場合はmpl::for_eachが便利です。
mpl::for_eachで扱えるよう、
template<class Struct> class read_member { public: read_member(const char* data, Struct& x) : data_(data), ptr_(&x) { } template<class Type, Type Struct::* PtrToMember, endianness E> void operator()(const member<Struct, Type, PtrToMember, E>&) { typedef member<Struct, Type, PtrToMember, E> member_type; binary_io_traits<E,Type>::read( data_ + member_offset<member_type>::type::value, ptr_->*PtrToMember ); } private: const char* data_; Struct* ptr_; };
みたいなファンクタを用意して、
template<endianness E, class T> struct binary_io_traits { static void read(const char* s, T& x) { typedef typename struct_traits<T>::members members; boost::mpl::for_each<members>(detail::read_member<T>(s, x)); } };
で各メンバを読み出しています。(writeも同じ)
あとは基本型や配列用にbinary_io_traitsを用意するだけです。
#構造体を入れ子にした場合にバグありです。
#入れ子構造体のサイズにsizeofを使うとパディングまで数えてしまうので。