C++のプログラムとRubyのプログラムの間でオブジェクトをやり取りする必要があったので、MessagePackを試した。
MessagePackとは
- オブジェクトシリアライズフォーマット
- 簡単に言うとバイナリのJSON。JSONのようにスキーマレスで使えるが、バイナリファイルなので圧縮効率が良い。
- 様々な言語でAPIが用意されており、複数の言語での情報のやり取りにも使える。
- 本家のページはこちら
Ruby APIのインストール方法
gemをインストールするだけ。
gem install msgpack
Rubyのサンプルコードはこのようになる。JSONのダンプ、ロードと同じくらいの気軽さで使える。
require 'msgpack'
msg = [1,2,3].to_msgpack #=> "\x93\x01\x02\x03"
MessagePack.unpack(msg) #=> [1,2,3]
C++ APIのインストール方法
githubのプロジェクトのページはこちら
このページに簡単なチュートリアルもある。
Macであればhomebrewで非常に簡単にインストールできる。/usr/local 以下にインストールされる。
brew install msgpack
g++ myfile.cpp -lmsgpack -o myfile.out
でコンパイルできる。
さらに、自前のクラスをダンプするときに便利なマクロ MSGPACK_DEFINE
がある。
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);
}
}
};
このようなクラスがあったときに、
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 を参照のこと。
C++からRubyにデータを渡す
#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;
}
require 'msgpack'
MessagePack.unpack( File.open("data.mpac") ) #=> [1, true, "example"]
RubyからC++にデータを渡す
File.open('data.mpac','w') {|io| io.print [1,true,"example"].to_msgpack }
#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++でデシリアライズする
File.open('data.mpac','w') { |io| io.print ({foo: 1.25, bar: "example", baz: true}).to_msgpack }
#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になる。)