最近 Cap'n Proto の導入例を割と見るようになっています。Cap'n ProtoはRPCのフレームワークおよびフォーマット仕様のシステムで、gRPCに対抗して作られたものです。
今回はCap'n Protoのスキーマ言語の定義と、その処理の簡単な例として、Serializationの最小限のサンプルを実装・提示します。
題材
パイプでのコマンド渡しでデータをやり取りする場合を例にします。
言語はCap'n Protoのデフォルト言語となっているC++にしました。
扱うデータのスキーマ定義
やり取りするデータの定義ファイルとして、demo.capnpのファイル名で次を用意します。
任意個数のタグ情報を持つテキストのリストをやり取りします。
#demo.capnp @0x9eb32e19f86ee174; using Cxx = import "/capnp/c++.capnp"; $Cxx.namespace("demo"); struct DemoData { name @0 :Text; tags @1 :List(Tag); struct Tag { name @0 :Text; param @1 :Text; } } struct DemoDataList { demodata @0 :List(DemoData); }
C++コードから参照する際に都合の良いように、namespace demoでコードを生成するよう冒頭で指定しています。
C++定義ファイルの生成
Cap'n Protoのスキーマ定義を、C++コードが参照可能なフォーマットに変換するため、以下コマンドを実行します。
capnp compile -o c++ demo.capnp
これにより「demo.capnp.c++」「demo.capnp.h++」が生成されます。
これをコンパイルしリンクすることで、C++コード上から、前述のスキーマ定義を用いたシリアライズやRPCが可能になります。
シリアライズの実装
データをスキーマ定義に基づいてシリアライズし、パイプで他ツールに渡すコードは次のようになります。
hoge、fugaのデータを、適宜のタグ情報を付与してシリアライズしています。
#include "addressbook.capnp.h" #include <capnp/message.h> #include <capnp/serialize-packed.h> #include <iostream> using demo::DemoData; using demo::DemoDataList; ... void output() { ::capnp::MallocMessageBuilder message; DemoDataList::Builder demoDataList = message.initRoot<DemoDataList>(); ::capnp::List<DemoData>::Builder demoData = demoDataList.initDemodata(2); DemoData::Builder hoge = demoData[0]; hoge.setName("hoge"); ::capnp::List<DemoData::Tag>::Builder hogeTags = hoge.initTags(2); hogeTags[0].setName("param1"); hogeTags[0].setParam("123"); hogeTags[1].setName("param2"); hogeTags[1].setParam("456"); DemoData::Builder fuga = demoData[1]; fuga.setName("fuga"); ::capnp::List<DemoData::Tag>::Builder fugaTags = fuga.initTags(1); fugaTags[0].setName("param1"); fugaTags[0].setParam("abc"); writePackedMessageToFd(1, message); } ...
デシリアライズの実装
前述のツールからパイプで送られてきた情報をデシリアライズし、表示するコードは次のようになります。
#include "addressbook.capnp.h" #include <capnp/message.h> #include <capnp/serialize-packed.h> #include <iostream> using demo::DemoData; using demo::DemoDataList; ... void read() { ::capnp::PackedFdMessageReader message(0); DemoDataList::Reader demoDataList = message.getRoot<DemoDataList>(); for (DemoData::Reader demoData : demoDataList.getDemodata()) { std::cout << demoData.getName().cStr() << std::endl; for (DemoData::Tag::Reader tag: demoData.getTags()) { std::cout << " " << tag.getName().cStr() << ":" << tag.getParam().cStr() << std::endl; } } } ...