Protocol Buffer を使ってみた

まだ全然ドキュメントとか読んでないけど。
マイコミジャーナルで紹介されてたのを読んでいたら、興味がわいてきた。
http://journal.mycom.co.jp/articles/2008/07/18/protocolbuffer/menu.html


まずはインストールから。
なんと、既に MacPorts でインストールできるようになっていたので、それでインストール。

$ sudo port install protobuf-cpp
$ rehash
$ protoc --version
libprotoc 2.0.0

パッケージ名からわかるように、C++ でしか使えるようになりません。
Java とかでも使えるようにする場合は、http://code.google.com/p/protobuf/ から手動でダウンロード&インストールすればいいのかな。
まぁ今回は C++ だけでいいや。


んで、とりあえずチュートリアル的なものを書いてみた。
まずはデータ構造を示す proto ファイルを書いた。
基本的な書き方は上記記事を参考。
foo.proto

package foo;  //C++ では、namespace にあたる

message Bar {  //C++ では、foo::Bar というクラスになる
  required string str = 1;  //Bar は str がもっていて、必ず設定する必要がある
  optional int32 number = 2;  //Bar は number をもっている。デフォルトは 0
  repeated Baz baz = 3;  //Bar は Baz をゼロ個以上もっている

  message Baz {  //C++ では、foo::Bar_Baz というクラスになる
    optional Type type = 1 [default = FUGA];  //Baz は type をもっている。デフォルトは FUGA(=1)

    enum Type {
      HOGE = 0;
      FUGA = 1;
      PIYO = 2;
    }
  }
}

続いて、このデータ構造を扱う C++ コードを生成する。

$ protoc foo.proto --cpp_out=.

これで、カレントディレクトリに

  • foo.pb.cc
  • foo.pb.h

というファイルができる。pb ってのは Protocol Buffer の略かな?
何故か実行属性がついてるけど、気にしないでおこう。


次に、新しくデータを生成するコードを書いた。
上記記事にはあまり詳しく書いてなかった、repeated なデータを扱うようなコードを書いた。
write.cpp

#include <iostream>
#include <fstream>
#include <string>
#include "foo.pb.h"

using namespace std;

int main(int argc, char *argv[])
{
  GOOGLE_PROTOBUF_VERIFY_VERSION;  //Protocol Buffer を扱う場合、最初にこれをやっておく

  foo::Bar bar;
  
  char fname[] = "foo.data";

  bar.set_str("なんとかかんとか");
  bar.set_number(55);

  foo::Bar_Baz *new_baz = bar.add_baz();  //新しく Bar_Baz を Bar の中で生成し、そのポインタを返す
  new_baz->set_type(foo::Bar_Baz::HOGE);

  new_baz = bar.add_baz();
  //何も設定しないと、デフォルトの foo::Bar_Baz::FUGA(=1) になる

  new_baz = bar.add_baz();
  new_baz->set_type(foo::Bar_Baz::PIYO);

  {
    ofstream fout(fname, ios::binary);
    if (!bar.SerializeToOstream(&fout)) {
      cerr << "Failed to serialize" << endl;
      return -1;
    }
  }
  return 0;
}

これをコンパイル&実行すれば、foo.data ができる。


最後に、foo.data を読むコードを書いた。
read.cpp

#include <iostream>
#include <fstream>
#include "foo.pb.h"

using namespace std;

int main(int argc, char *argv[])
{
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  foo::Bar bar;
  
  char fname[] = "foo.data";

  {
    ifstream fin(fname, ios::binary);
    if (!fin) {
      cout << fname << " does not exist" << endl;
      return -1;
    } else if (!bar.ParseFromIstream(&fin)) {
      cerr << "Failed to parse " << fname << endl;
      return -1;
    }
  }

  cout << "bar.str: " << bar.str() << endl;
  cout << "bar.number: " << bar.number() << endl;

  int size = bar.baz_size();  //ちょっとした疑問。なんで unsigned int じゃないんだろ?
  for (int i = 0; i < size; i++) {
    cout << "bar.baz[" << i << "].type: " << bar.baz(i).type() << endl;
  }
  //他にも、STL-like なイテレータを使って同じことができるみたい

  return 0;
}

これをコンパイル&実行。

$ ./read
bar.baz: なんとかかんとか
bar.number: 55
bar.baz[0].type: 0
bar.baz[1].type: 1
bar.baz[2].type: 2

ちゃんと読み書きできました。


最低限の機能はこんなカンジ。
XML とかにはない機能もあるみたいだから、まだまだ興味は尽きない。
まずは C++ から使う上でのドキュメントを読まねば。ちょうど明後日まで休みだしね。