RubyとC++でMessagePack


C++のプログラムとRubyのプログラムの間でオブジェクトをやり取りする必要があったので、MessagePackを試した。

## MessagePackとは

- オブジェクトシリアライズフォーマット
- 簡単に言うとバイナリのJSON。JSONのようにスキーマレスで使えるが、バイナリファイルなので圧縮効率が良い。
- 様々な言語でAPIが用意されており、複数の言語での情報のやり取りにも使える。
- 本家のページは[こちら](http://msgpack.org/)

## Ruby APIのインストール方法

gemをインストールするだけ。

```sh
gem install msgpack
```

Rubyのサンプルコードはこのようになる。JSONのダンプ、ロードと同じくらいの気軽さで使える。

```ruby
require 'msgpack'
msg = [1,2,3].to_msgpack  #=> "\x93\x01\x02\x03"
MessagePack.unpack(msg)   #=> [1,2,3]
```

## C++ APIのインストール方法

githubのプロジェクトのページは[こちら](https://github.com/msgpack/msgpack-c)
このページに簡単なチュートリアルもある。

Macであればhomebrewで非常に簡単にインストールできる。/usr/local 以下にインストールされる。

```sh
brew install msgpack
```

```sh
g++ myfile.cpp -lmsgpack -o myfile.out
```

でコンパイルできる。


さらに、自前のクラスをダンプするときに便利なマクロ `MSGPACK_DEFINE` がある。


```c++
class myclass {
private:
    std::string m_str;
    std::vector<int> m_vec;
public:
    MSGPACK_DEFINE(m_str, m_vec);
    void Print() {
      std::cout << m_str << std::endl;
      for( std::vector<int>::iterator it = m_vec.begin(); it != m_vec.end(); ++it) {
        std::cout << *it << std::endl;
      }
    }
    myclass() {}
    myclass(const std::string & str, int max) {
      m_str = str;
      for( int i=0; i < max; i++) {
        m_vec.push_back(i);
      }
    }
};

```

このようなクラスがあったときに、

```c++
        std::vector<myclass> vec;
        // add some elements into vec...
        vec.push_back( myclass("foo", 3) );
        vec.push_back( myclass("bar", 0) );
        vec.push_back( myclass("baz", 2) );
        // you can serialize myclass directly
        msgpack::sbuffer sbuf;
        msgpack::pack(sbuf, vec);
```

このように簡単にシリアライズできる。デシリアライズも同様に簡単。
詳しくは本家の[Quick Start](https://github.com/msgpack/msgpack-c/blob/master/QUICKSTART-CPP.md) を参照のこと。

## C++からRubyにデータを渡す

```C++
#include <string>
#include <iostream>
#include <fstream>
#include <msgpack.hpp>

int main() {

  msgpack::type::tuple<int, bool, std::string> src(1, true, "example");
  std::ofstream ofs("data.mpac");
  msgpack::pack( ofs, src );

  return 0;
}
```

```ruby
require 'msgpack'
MessagePack.unpack( File.open("data.mpac") )   #=> [1, true, "example"]
```

## RubyからC++にデータを渡す

```ruby
File.open('data.mpac','w') {|io| io.print [1,true,"example"].to_msgpack }
```

```c++
#include <string>
#include <iostream>
#include <iterator>
#include <fstream>
#include <msgpack.hpp>

int main() {

  std::ifstream ifs("data.mpac");
  std::istream_iterator<char> first(ifs);
  std::istream_iterator<char> last;

  const std::string data(first, last);  // read file

  msgpack::unpacked msg;
  msgpack::unpack( &msg, data.data(), data.size() );

  msgpack::object obj = msg.get();
  std::cout << obj << std::endl;   // => [1, true, "example"]

  msgpack::type::tuple<int, bool, std::string> converted;
  obj.convert(&converted);
  std::cout << converted.get<0>() << std::endl;  // => 1
  std::cout << converted.get<1>() << std::endl;  // => 1
  std::cout << converted.get<2>() << std::endl;  // => "example"

  return 0;
}
```

## RubyのHashをC++でデシリアライズする

```ruby
File.open('data.mpac','w') { |io| io.print ({foo: 1.25, bar: "example", baz: true}).to_msgpack }
```

```c++
#include <string>
#include <iostream>
#include <iterator>
#include <fstream>
#include <msgpack.hpp>

int main() {
  
  std::ifstream ifs("data.mpac");
  std::istream_iterator<char> first(ifs);
  std::istream_iterator<char> last;

  const std::string data(first, last);  // read file

  msgpack::unpacked msg;
  msgpack::unpack( &msg, data.data(), data.size() );

  msgpack::object obj = msg.get();
  std::cout << obj << std::endl;

  typedef std::map< std::string, msgpack::object > MapStrMsgpackObj;
  MapStrMsgpackObj mmap = obj.as<MapStrMsgpackObj>();
  std::cout << mmap.find("foo")->second.as<double>() << std::endl;      // => 1.25
  std::cout << mmap.find("bar")->second.as<std::string>() << std::endl; // => "example"
  std::cout << mmap.find("baz")->second.as<bool>() << std::endl;        // => 1

  return 0;
}
```

rubyのHashのkeyはstring型とする。（上のコードはキーがシンボルだが、msgpackに変換されたときにstringになる。）