c++でmsgpackを使ってみる

msgpackとは

MessagePack: It's like JSON. but fast and small.

MessagePackは、効率の良いバイナリ形式のオブジェクト・シリアライズ フォーマットです。
JSONの置き換えとして使うことができ、様々なプログラミング言語をまたいでデータを交換することが可能です。
しかも、JSONよりも速くてコンパクトです。
例えば、小さな整数値はたった1バイト、短い文字列は文字列自体の長さ+1バイトでシリアライズできます。

らしい

仕事で使う機会があったのでc++でサンプルコードを実行してみる

msgpackのインストール

MessagePack: It's like JSON. but fast and small.C/C++ msgpack のページに従う

準備

` インストール条件

gcc >= 4.1.0
cmake >= 2.8.0

gccバージョン確認

$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04.3' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) 

cmake確認

なかったのでインストール

sudo apt-get install cmake 
$ cmake -version
cmake version 2.8.12.2

msgpackのインストール

msgpackのgitはここ

github.com

$ git clone https://github.com/msgpack/msgpack-c.git
$ cd msgpack-c
$ cmake .
$ make
$ sudo make install

msgpackのバージョンを確認

msgpackのサンプルコードを実行

msgpack-c/QUICKSTART-CPP.md at master · msgpack/msgpack-c · GitHub

First program(単なるデータのシリアライズとデシリアライズ

コード

以下を hello.cpp という名前で保存

#include <msgpack.hpp>
#include <vector>
#include <string>
#include <iostream>

int main(void) {
        // serializes this object.
        std::vector<std::string> vec;
        vec.push_back("Hello");
        vec.push_back("MessagePack");

        // serialize it into simple buffer.
        msgpack::sbuffer sbuf;
        msgpack::pack(sbuf, vec);

        // deserialize it.
        msgpack::object_handle oh =
            msgpack::unpack(sbuf.data(), sbuf.size());

        // print the deserialized object.
        msgpack::object obj = oh.get();
        std::cout << obj << std::endl;  //=> ["Hello", "MessagePack"]

        // convert it into statically typed object.
        std::vector<std::string> rvec;
        obj.convert(rvec);
}

コンパイル

$ g++ -Ipath_to_msgpack/include hello.cpp -o hello

実行
 $ ./hello 
["Hello", "MessagePack"]

Streaming into an array or map(配列やマップのシリアライズとデシリアライズ

msgpack-c/QUICKSTART-CPP.md at master · msgpack/msgpack-c · GitHub

ここにはシリアライズはあるが、デシリアライズの処理がないので、自分で作ってみる

$ cat stream.cpp 
#include <msgpack.hpp>
#include <iostream>
#include <string>

#include <sstream>

int main(void) {
        // serializes multiple objects into one message containing an array using msgpack::packer.
        msgpack::sbuffer buffer;

        msgpack::packer<msgpack::sbuffer> pk(&buffer);
        pk.pack_array(4);
        pk.pack(std::string("Log message ... 1"));
        pk.pack(std::string("Log message ... 2"));
        pk.pack(std::string("Log message ... 3"));
        pk.pack(std::string("Log message ... 4"));

        // serializes multiple objects into one message containing a map using msgpack::packer.
        msgpack::sbuffer buffer2;

        msgpack::packer<msgpack::sbuffer> pk2(&buffer2);
        pk2.pack_map(2);
        pk2.pack(std::string("x"));
        pk2.pack(3);
        pk2.pack(std::string("y"));
        pk2.pack(3.4321);
//        pk2.pack(4);


        /*---- 以下自作部分 ----*/
        
        // unpack array
        msgpack::unpacked msg;
        msgpack::unpack(msg, buffer.data(), buffer.size());
        msgpack::object obj = msg.get();
        msgpack::object_array obj_array = obj.via.array;

        std::string str[4];
        for (int i = 0; i < 4; i++) {
             (obj_array.ptr[i]).convert(str[i]);
             std::cout << "str[" << i << "]: " << str[i] << std::endl;
        }

        // unpack map
        msgpack::unpacked msg2;
        msgpack::unpack(msg2, buffer2.data(), buffer2.size());
        msgpack::object obj2 = msg2.get();
        msgpack::object_map obj_map = obj2.via.map;

        for (int i = 0; i < 2; i++) {
            std::string map_key;
            //std::cout << "key: " << obj_map.ptr[i].key;
            (obj_map.ptr[i].key).convert(map_key);


            // 以下、mapのvalの方は、intの場合とfloatの場合があるので一旦floatにconvert
            // してからstringに変換した
            float f;
            // obj_map.ptr[i].valはmsgpackのobject
            // 一旦floatにconvertしてからそれを文字列に変換した
            (obj_map.ptr[i].val).convert(f);
            std::stringstream ss;
            ss << f;

            std::cout << "key: " << map_key << " val: " << ss.str() << std::endl;


            // これを実行するとintは2, floatは4と表示される
            //std::cout << "type: " << (obj_map.ptr[i].val).type << std::endl;

            // 以下のようなif文で判定できるが、このプログラムでは一括してfloatに変換している
            //if ( (obj_map.ptr[i].val).type == msgpack::type::POSITIVE_INTEGER ) {
            //}
        }
        

        //  $ g++ -Ipath_to_msgpack/include stream.cpp -o stream
}

実行結果

$ g++ -Ipath_to_msgpack/include stream.cpp -o stream
$ ./stream 
str[0]: Log message ... 1
str[1]: Log message ... 2
str[2]: Log message ... 3
str[3]: Log message ... 4
key: x val: 3
key: y val: 3.4321

上の修正は以下を参考にした

msgpackのオブジェクトとしてここを参考にした

    MSGPACK_OBJECT_NIL                  = 0x00,
    MSGPACK_OBJECT_BOOLEAN              = 0x01,
    MSGPACK_OBJECT_POSITIVE_INTEGER     = 0x02,
    MSGPACK_OBJECT_NEGATIVE_INTEGER     = 0x03,
    MSGPACK_OBJECT_FLOAT32              = 0x0a,
    MSGPACK_OBJECT_FLOAT64              = 0x04,
    MSGPACK_OBJECT_FLOAT                = 0x04,
#if defined(MSGPACK_USE_LEGACY_NAME_AS_FLOAT)
    MSGPACK_OBJECT_DOUBLE               = MSGPACK_OBJECT_FLOAT, /* obsolete */
#endif /* MSGPACK_USE_LEGACY_NAME_AS_FLOAT */
    MSGPACK_OBJECT_STR                  = 0x05,
    MSGPACK_OBJECT_ARRAY                = 0x06,
    MSGPACK_OBJECT_MAP                  = 0x07,
    MSGPACK_OBJECT_BIN                  = 0x08,
    MSGPACK_OBJECT_EXT                  = 0x09              
  • 注意点として、msgpackのバージョンによるのか、関数名や引数が参照渡しかポインタ渡しかなどが違うみたい
  • 単純に例と同じようにやってもうまく行かなかった

User-defined classes(MSGPACK_DEFINE)

msgpack-c/QUICKSTART-CPP.md at master · msgpack/msgpack-c · GitHub

You can use serialize/deserializes user-defined classes using MSGPACK_DEFINE macro.

とある通り、ユーザ定義のクラスをMSGPACK_DEFINEというマクロを使ってシリアライズ/デシリアライズできる

$ cat msgpack_define.cpp
#include <msgpack.hpp>
#include <vector>
#include <string>

#include <iostream>

class myclass {
//private:
//    std::string m_str;
//    std::vector<int> m_vec;
public:
    std::string m_str;
    std::vector<int> m_vec;

    MSGPACK_DEFINE(m_str, m_vec);

    myclass() {
        m_str = "default";
        m_vec.push_back(1);
        m_vec.push_back(2);
        m_vec.push_back(3);
    }
};

int main(void) {
        std::vector<myclass> vec;
        // add some elements into vec...

        vec.push_back(myclass());


        // you can serialize myclass directly
        msgpack::sbuffer sbuf;
        msgpack::pack(sbuf, vec);

        msgpack::object_handle oh =
            msgpack::unpack(sbuf.data(), sbuf.size());

        msgpack::object obj = oh.get();

        // you can convert object to myclass directly
        std::vector<myclass> rvec;
        obj.convert(rvec);

        for(std::vector<myclass>::iterator it = rvec.begin(); it != rvec.end(); ++it) {
            std::cout << (*it).m_str << std::endl;

            for(std::vector<int>::iterator m_vec_it = (*it).m_vec.begin(); m_vec_it != (*it).m_vec.end(); ++m_vec_it) {
                std::cout << (*m_vec_it) << std::endl;
            }
        }
}

実行結果

$ g++ -Ipath_to_msgpack/include msgpack_define.cpp -o msgpack_define
$ ./msgpack_define 
default
1
2
3

MSGPACK_DEFINEの定義

v1_1_cpp_adaptor · msgpack/msgpack-c Wiki · GitHub

ソースコードは以下 - https://github.com/msgpack/msgpack-c/blob/55b51c506fee9ce496e9b98aca33cadade681479/include/msgpack/adaptor/define_decl.hpp#L28-L42

#define MSGPACK_DEFINE_ARRAY(...) \
    template <typename Packer> \
    void msgpack_pack(Packer& pk) const \
    { \
        msgpack::type::make_define_array(__VA_ARGS__).msgpack_pack(pk); \
    } \
    void msgpack_unpack(msgpack::object const& o) \
    { \
        msgpack::type::make_define_array(__VA_ARGS__).msgpack_unpack(o); \
    }\
    template <typename MSGPACK_OBJECT> \
    void msgpack_object(MSGPACK_OBJECT* o, msgpack::zone& z) const \
    { \
        msgpack::type::make_define_array(__VA_ARGS__).msgpack_object(o, z); \
    }

同じソースコードに下に以下も定義されているので、MSGPACK_DEFINE がMSGPACK_DEFINE_ARRAYに置き換わる
#define MSGPACK_DEFINE MSGPACK_DEFINE_ARRAY