2010-07-24

google発のProtocol Buffersについて

オブジェクトシリアライズツールであるプロトコルバッファについて書きます。

プロトコルバッファって何って方はこちらへ

Protocol Buffers 本家

http://code.google.com/apis/protocolbuffers/

XMLはもう不要!? Googleシリアライズツール「Protocol Buffer」

http://journal.mycom.co.jp/articles/2008/07/18/protocolbuffer/index.html

Protocol Buffers (Protocol Buffers の内部解説記事。とても参考になります)

http://dodgson.org/omo/t/?date=20080712

内容

プロトコルバッファは異種言語間でオブジェクトのやりとりをするための規格です。

独自の言語によりオブジェクトインターフェースを規定することで、多言語対応を行っています。

例えばこんな感じ。

  • address.proto
package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;        // Unique ID number for this person.
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person person = 1;
}

以上のようなprotoファイルから各言語ソースコード、または何らかのデータ操作ライブラリを使いオブジェクトの処理を行います。

googleによってC++, Java, Python用のライブラリ作成されましたが、他の言語対応したサードパーティー製のライブラリがいくらでもあるので、実質的にほぼすべての言語で使えると言っても過言ではありません。

以下はこのライブラリを使ってみた感想などです。

整数型はVarintという可変長型でバイナリに保存される

数字が多きければ大きいほど、長いバイト長で保存されます。ただし、負数の場合符号ビットが立つ関係で、ほとんど常に変換後のバイト数が最長バイト数(10)になってしまいます。フィールドの型をsint32, sint64で宣言しると、各数値にzig-zags変換が行われるため、負数であってもその値の絶対値で使用バイト数が決まるようになります。

保存されるデータは各メッセージID/型/値のみ

バイナリに保存されるデータは各メッセージID/型/値のみです。なので、同じ定義の二つのメッセージ型は、プロトコルバッファ上では全く同じように扱うことが出来ます。例えば、片方からシリアライズしたデータを、もう片方の型でデシリアライズすることが可能です。

またオブジェクト連続シリアライズ/デシリアライズすることもできます。

継承されたクラスマッピング

すでに存在する継承関係のあるクラスを、Protocol Buffersでシリアライズ/デシリアライズしたい場合は次のようにします。

(Base, Derived はすでに存在するとします)

(ソースコード中になぜか日本語が書けないので、コメントはすべて英語になっています)

message PbBase {
        require int32 id = 1;
        require int32 value = 2;

        require Derived derived = 10; // - Point !!!
}

message PbDerived {
        require string string_value = 1;
}

継承元のメッセージ定義に、継承先のメッセージを持たせます。Base継承するクラスシリアライズ/デシリアライズしたい場合は、PbBaseメッセージを中心に処理を行うことで、比較的簡単に処理を実装することが出来ます。

例えばこんな感じ

Base *Base_DeserializeFrom(PbBase &pbobj)
{
    // Arrange the classes which inherits from Base.
    if (pbobj.has_derived()) {
        return new Derived(pbobj);
    }
    else
    ...
}

class Base {
    ...
    virtual void Base::SerializeTo(PbBase &pbobj) {
        // Set the fields of 'pbobj',
    }
    ...
};

class Derived {
    ...
    virtual void Base::SerializeTo(PbBase &pbobj)
    {
        PbDerived *derived = pbobj.mutable_derived();

        Base::SerializeTo(pbobj);
        // Set the fields of 'derived',
        ...
    }
    ...
};

protoファイルを以下のように書くと、メッセージの扱いが非常に難しくなります。

message PbBase {
        require int32 id = 1;
        require int32 value = 2;
}

message PbDerived {
        required PbBase base = 1; // - Here is the point !!!
        require string string_value = 2;
}

記事への反応(ブックマークコメント)

ログイン ユーザー登録
ようこそ ゲスト さん