C++とRubyでProtocol Buffers


## やりたいこと
C++とRubyでデータをやり取りする。シリアライズ形式としてProtocol Buffersを使用する。

## Protocol Buffersとは

[Wikipedia](http://ja.wikipedia.org/wiki/Protocol_Buffers) によると "Protocol Buffers(プロトコルバッファー)はインタフェース定義言語(IDL)で構造を定義する通信や永続化での利用を目的としたシリアライズフォーマットであり、Googleにより開発されている。"

MessagePackと異なる点は、IDLを事前に定義する必要があるという点。IDLをコンパイルして各言語用のクラス定義を作成する。
事前にIDLを書かなければならないというのは利点でも欠点でもある。
MessagePackやJSONほどの表現の柔軟性は無いが、C++などの静的型付け言語で書くときにはデータのシリアライズ・デシリアライズのコードが比較的簡単に明瞭なコードが書けるという印象。自然なインターフェースのクラスが自動的に生成されるのは嬉しい。
動的片付けの言語の場合は、圧倒的にMessagePackの方が楽。

ProtocolBuffersはStreaming機能が提供されていない。そのため、大きなデータ（1MByte以上）の場合、処理が重くなる。（データがメモリにコピーされるため）
Protocol Buffersでシリアライズした形式を連続して書き出して自前でStreaming処理を記述する事もできるが、少々面倒そう。
(https://developers.google.com/protocol-buffers/docs/techniques)

## 導入方法

### C++ APIの導入方法
macであればhomebrewで簡単に入れられる。

```sh
brew install protobuf
```

ライブラリ、ヘッダに加えて、protocというコマンドがインストールされる。

### Ruby APIの導入方法
- [ruby-protobuf](https://code.google.com/p/ruby-protobuf/) というgemを使う。

```sh
gem install ruby_protobuf
```

ライブラリに加えて `rprotoc` というコマンドもインストールされる。これが.protoをRubyのクラスに変換するコンパイラ。

## 使用方法

### C++ APIの使用方法
以下のような.protoファイル(IDL)を用意し、protocコマンドでコンパイルする。

```proto:person.proto
message Person {
  required int32 id = 1;
  required string name = 2;
  optional string email = 3;
}
```

```sh
protoc person.proto --cpp_out=.
```

--cpp_out は出力ディレクトリの指定。指定した出力ディレクトリに `person.pb.h`, `person.pb.cc` という二つのファイルが生成される。
このファイルをインクルードすれば、シリアライズ・デシリアライズが可能になる。

まずはシリアライズのサンプルを以下に示す。

```cpp:sample_write.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "person.pb.h"

int main() {

  GOOGLE_PROTOBUF_VERIFY_VERSION;

  Person person;

  person.set_id(12345);
  person.set_name("foobar");
  person.set_email("foobar@example.com");

  std::ofstream fout("person_foobar.pb", std::ios::binary);
  person.SerializeToOstream(&fout);

  return 0;
}
```

このように非常に素直なインターフェースを持ったPersonクラスが作成される。
`GOOGLE_PROTOBUF_VERIFY_VERSION` は厳密には必要ないが、プログラムの最初に書いておくとリンクするProtocol Buffersのバージョンに互換性の問題が無いかチェックしてくれる。とりあえずおまじないとして書いておくのが良さそう。

コンパイルはこのようにする。

```sh
g++ sample_write.cpp person.pb.cc -lprotobuf
```

デシリアライズのサンプルは以下の通り

```cpp:sample_read.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "person.pb.h"

int main() {

  GOOGLE_PROTOBUF_VERIFY_VERSION;

  Person person;

  std::ifstream fin("person_foobar.pb", std::ios::binary);
  bool succeeded = person.ParseFromIstream(&fin);
  if( !succeeded ) { std::cerr << "Failed to parse file" << std::endl; }

  std::cout << "id:    " << person.id() << std::endl
            << "name:  " << person.name() << std::endl
            << "email: " << person.email() << std::endl;

  return 0;
}
```

これでシリアライズ、デシリアライズができるが、これらの生成されたクラスを継承などで拡張していくのは良い作法では無さそう。
あくまでI/Oのためのクラスと割り切って使い、プログラムで使う際には別のクラスにデータを渡すのが良いだろう。

### Ruby APIの使用方法

Rubyから使うためには `ruby_protobuf` gemを使う。

```sh
gem install ruby_protobuf
```

C++の時と同様に、protoファイルを`rprotoc`コマンドでコンパイルする。`person.pb.rb` というファイルが作られる。 

```sh
rprotoc person.proto
```

```rb:sample.rb
require "./person.pb.rb"
person = Person.new
person.parse_from_file("person_foobar.pb")
p person  #=> id: 12345 name: "foobar" email: "foobar@example.com"
person.serialize_to_file('dumped.pb')
```

もちろんC++とのI/Oも問題ない。