ま、そんなところで。

ニッチな技術系メモとか、車輪を再発明してみたりとか.

C++でMessagePackを使ってみる(5)〜 msgpack::objectの読み書き 〜

msgpack::object を直接読み書きする

カスタムアダプタが対象とする型の書き込み実装では、個々のメンバについてobject化を定義しなければなりません.
ほとんどの場合はすでに定義されている別のobjectのオーバーロードやカスタムアダプタへ委譲することができますが、複雑なユーザ定義型など委譲できるアダプタがないような場合は、自分で直接objectへの書き込みを定義することになります.

ここでは、既存のアダプタへ委嬢しないで直接読み書きを定義するための情報をまとめておきます.

各種コンテナの型とタイプID

ほとんどのC++オブジェクトは別のオブジェクトを内部に持つ構造をしていて、データもプリミティブ型だけでなく文字列やバイト列、別のユーザ定義型など様々です.
れらのデータ型を適切にmsgpack::objectに変換してやらなければなりません.

msgpack::objectの定義はこのような感じです.

struct object {
    union union_type {
        bool boolean; // bool型
        uint64_t u64;  // 符号なし整数
        int64_t  i64; // 符号あり整数
        double   f64; // 浮動小数点小数型
        msgpack::object_array array; // array
        msgpack::object_map map;     // map
        msgpack::object_str str;     // char文字列型
        msgpack::object_bin bin;     // バイナリ型
        msgpack::object_ext ext;     // 外部メモリ直接参照型
    };
    msgpack::type::object_type type; // タイプID
    union_type via; // データ
};

boolean, u64, i64, f64はそれぞれ、boolや符号あり/なし整数, 浮動小数点小数を格納します.
この他にコレクションコンテナとして array, map, str, bin, extがあります.

typeには、は via に設定した値に対応する以下の enum 値をタイプIDとして指定します.

enum object_type {
    NIL                 , // nullを示す時指定する
    BOOLEAN             , // boolean
    POSITIVE_INTEGER    , // i64
    NEGATIVE_INTEGER    , // i64
    FLOAT32             , // f64
    FLOAT64             , // f64
    STR                 , // str
    BIN                 , // bin
    ARRAY               , // array
    MAP                 , // map
    EXT                 , // ext
};

object_str の読み書き

マルチバイト文字列を格納するための構造体.
データアドレスとサイズを持ちます.
読み出し時は構造体のデータから目的のオブジェクトを復元することになります.
書き込み時は、データ本体をzone上に置き、構造体には文字列の長さとzone上のメモリアドレスを指定します.

// str 構造体
struct object_str {
    uint32_t size; // サイズ
    const char* ptr; // 文字列のポインタ(データ本体はzone上に置く)
};

zoneを使って文字列をobject_strへ格納する実装はこんな感じになります.

#include <msgpack/versioning.hpp>
#include <msgpack/adaptor/adaptor_base.hpp>
#include <msgpack/adaptor/check_container_size.hpp>
    :
    :
std::string mystring("hogefuga");

msgpack::zone z;
// サイズ
uint32_t byte_size = checked_get_container_size(mystring.length());
// typeはSTR
o.type = msgpack::type::STR;
// zone上に文字列用のアライメント済みメモリを確保.
char* ptr = static_cast<char*>(z.allocate_align(byte_size, MSGPACK_ZONE_ALIGNOF(char)));
// 文字列をzone上の領域へコピー
std::memcpy(ptr, mystring.c_str(), mystring.length());
// strオブジェクトに設定
o.via.str.ptr = ptr;
o.via.str.size = mystring.length();

復元するときは文字列とサイズから文字列オブジェクトを再構築します.

//
// msgpack::object o
//

// 文字列
std::string s;
// 必ずタイプをチェックする
switch (o.type) {
case msgpack::type::STR:
    s.assign(o.via.str.ptr, o.via.str.size);
    break;
default:
    throw type_error();
}

object_bin の読み書き

バイナリデータを保持するための構造体.
strと同じように使います.

// bin構造体
struct object_bin {
    uint32_t size; // サイズ
    const char* ptr; // データのポインタ(データ本体はzone上に置く)
};

バイナリデータの書き込みはこのような感じです.
ほとんど文字列と同じですね.

#include <msgpack/versioning.hpp>
#include <msgpack/adaptor/adaptor_base.hpp>
#include <msgpack/adaptor/check_container_size.hpp>
    :
    :
// char* pdata
// size_t data_size

msgpack::zone z;
// サイズ
uint32_t byte_size = checked_get_container_size(data_size);
// typeはBIN
o.type = msgpack::type::BIN;
// zone上にアライメント済みメモリを確保してコピー
char* ptr = static_cast<char*>(z.allocate_align(byte_size, MSGPACK_ZONE_ALIGNOF(char)));
std::memcpy(ptr, pdata, data_size);
// strオブジェクトに設定
o.via.bin.ptr = ptr;
o.via.bin.size = data_size;

復元するときも文字列と同じような感じです.

//
// msgpack::object o
//

std::vector<char> v;
switch (o.type) {
case msgpack::type::BIN:
    v.assign(o.via.bin.ptr, o.via.bin.size);
    break;
default:
    throw type_error();
}

object_array の読み書き

arrayの場合は、objectの配列をallocateして各要素にobjectを書き込みます.
arrayというのはあくまでmsgpack::objectの配列で、objectが格納している型までは全て同じでなくても構いません.

// array構造体
struct object_array {
    uint32_t size; // object配列の長さ
    msgpack::object* ptr; // object配列ポインタ(object配列本体はzone上に置く)
};

シリアライズの例.

#include <msgpack/versioning.hpp>
#include <msgpack/adaptor/adaptor_base.hpp>
#include <msgpack/adaptor/check_container_size.hpp>
    :
    :
// size_t dataLen = 2

// object配列のメモリを確保
msgpack::object* p = static_cast<msgpack::object*>(
                         o.zone.allocate_align(sizeof(msgpack::object) * dataLen,
                                               MSGPACK_ZONE_ALIGNOF(msgpack::object))
                     );
o.type = msgpack::type::ARRAY;
o.via.array.size = dataLen;
o.via.array.ptr  = p;

if (p) {
    // [0]にuint32_tを書き込む
    int32_t num(16);
    o.via.array.ptr[0] = msgpack::object(num); // int32_tのobject
    // [1]にuint64_tを書き込む
    uint64_t u64value(1234);
    o.via.array.ptr[1] = msgpack::object(u64value); // uint64_tのobject
}

シリアライズはこのような感じになります.
それぞれのobjectからasもしくはconvertでデータを取り出します.

//
// msgpack::object o
//

uint32_t num(0);
uint64_t val(0);
// typeチェック.
// 期待するタイプでなければ type_error 例外を投げる.
switch (o.type) {
case msgpack::type::ARRAY:
    {
        auto cnt = o.via.array.size;
        if (cnt != 2)
            throw type_error();

        // objectのconvertでデシリアライズ.
        o.via.array.ptr[0].convert(num);
        // objectのasでデシリアライズ.
        val = o.via.array.ptr[1].as<uint64_t>();
    }
    break;
default:
    throw type_error();
}

object_map の読み書き

mapの場合は、object_kvの配列をallocateして各要素のkeyとvalueに対応するobjectを書き込みます.
arrayのときと同じく確保するのはobject_kvの配列で、object_kvが格納するkeyとvalは全て同じ型の組み合わせでなくてもOKです.

// key-value構造体
struct object_kv {
    msgpack::object key; // keyとなるobject
    msgpack::object val; // valueとなるobject
};

// map要素構造体
struct object_map {
    uint32_t size; // サイズ
    msgpack::object_kv* ptr; // object_kv配列のポインタ(本体はzone上に置く)
};

シリアライズの例.

#include <msgpack/versioning.hpp>
#include <msgpack/adaptor/adaptor_base.hpp>
#include <msgpack/adaptor/check_container_size.hpp>
    :
    :
// char* pdata
// size_t dataLen = 3

msgpack::object_kv* p = static_cast<msgpack::object_kv*>(
                            o.zone.allocate_align(sizeof(msgpack::object_kv) * dataLen,
                                                  MSGPACK_ZONE_ALIGNOF(msgpack::object_kv))
                        );
o.type = msgpack::type::MAP;
o.via.map.size = dataLen; // 3
o.via.map.ptr  = p;

if (p) {
    // keyにuint32_tを書き込む
    // valにuint64_tを書き込む
    int32_t num(16);
    uint64_t u64value(1234);
    o.via.map.ptr[0].key = msgpack::object(num);
    o.via.map.ptr[0].val = msgpack::object(u64value);
       :
       :
}

シリアライズの例

//msgpack::object o
//

uint32 num(0);
uint64 val(0);
// typeチェック.
// 期待するタイプでなければ type_error 例外を投げる.
switch (o.type) {
case msgpack::type::MAP:
    {
        auto cnt = o.via.map.size;
        if (cnt != 3)
            throw type_error();

        // 各々keyとvalueのconvertをコールして復元する.
        o.via.map.ptr[0].key.convert(num);
        o.via.map.ptr[0].val.convert(val);
                 :
                 :
    }
    break;
default:
    throw type_error();
}

参考記事