#目的
SBE(Simple-Binary-Encoding)のC++のサンプルをビルドし実行する。
#SBEの概要
Protocol bufferとかMessagePackとかの類。(MessagePackは使ったことがないけど) xmlでデータスキーマを定義してSBEを走らせれば対応するシリアライザ・デシリアライザをターゲットの言語で生成できる。生成できる言語はJava、C++、Go、C#らしい。速いのが特徴らしいが比較したことはないのでわからない。個人的にprotocol bufferで生成されたC++コードがprotocol bufferがビルド・インストールされた環境でしかビルドできず(hackishなことをすればできるが・・)、ミニマルなLinux環境で使いづらかったのに対して、SBEの生成したコードはSBEがビルドされた環境とは無関係にビルドできるところが結果的に気に入ったので、使ってみようかと思っている。
#環境
Ubuntu16.04でopenjdk-8である。openjdk-9ではうまく行かなかったのでopenjdk-8にした。他にやり方があるかもしれない。Ubuntu 14.04と18.04では最初からうまく行った。
その他にも依存関係があるかもしれないが、今のところわからない。
また、この記事ではホームディレクトリ(/home/yhmtmt)で作業することとする。
#ビルド
SBEのgithubからreleaseの1.12.2をダウンロード、展開してできるディレクトリ(simple-binary-encoding-1.12.2)でgradlewを実行するだけ。
yhmtmt@ubuntu1604: /home/yhmtmt/simple-binary-encoding-1.12.2$ ./gradlew
#エンコーダー・デコーダーの生成
ビルド後にsimple-binary-encoding-1.12.2/sbe-allの中にあるsbe-all-1.12.2.jarを使ってエンコーダー・デコーダーを生成する。とりあえず、スキーマの定義ファイルのサンプルはsimple-binary-encoding-1.12.2/sbe-samples/src/main/resources/example-schema.xmlを使う。しかし、ここでSBEのgithubのREADMEにある通りのコマンドで実行するとエラーになる。(正しく実行できる方法を探すのに苦労するので、備忘録代わりにこんな記事を書いているのでもある)正しくは次のように実行する。
yhmtmt@ubuntu1604: /home/yhmtmt/simple-binary-encoding-1.12.2/sbe-samples/src/main/resources $ java -Dsbe.output.dir=/home/yhmtmt/work -Dsbe.generate.ir=true -Dsbe.target.language=cpp -Dsbe.xinclude.aware=true -jar /home/yhmtmt/simple-binary-encoding-1.12.2/sbe-all/build/libs/sbe-all-1.12.2.jar /home/yhmtmt/simple-binary-encoding-1.12.2/sbe-samples/src/main/resources/exampl
e-schema.xml
要点は次の通り。
- __出力先が-Dsbe.output.dir=/home/yhmtmt/workと指定してある。__このパスにbaselineというディレクトリが作られ、エンコーダー・デコーダーがC++のヘッダーファイルの形で作られる。(これをしばしばスタブと呼ぶ)
- __Xincludeを-Dsbe.xinclude.aware=trueでイネーブルする。__これがないとエラーになる。というのは、example-schema.xmlがcommon-types.xmlをインクルードしていて、これを展開するためにXincludeをイネーブルする必要があるようだ。このことが分かるまでに時間がかかった。
- __あらかじめスキーマのxmlファイルがあるディレクトリに移動しておく。__つまりこの場合、/home/yhmtmt/simple-binary-encoding-1.12.2/sbe-samples/src/main/resourcesに移動しておく。どうやらexample-schema.xmlがインクルードしている別のスキーマファイルのパスをカレントからしか探せないようだ。(これは次のバージョンでは修正されているかもしれない。スナップショットの1.12.3だとスキーマ定義ファイルの場所に移動する必要がなかった。)
#サンプルのビルド
simple-binary-encoding-1.12.2/sbe-samples/src/main/cppの下にGeneratedStubExample.cppなるソースがあり、内容は単純に、先にexample-schema.xmlから生成したヘッダファイルを使ってデータをエンコードしてデコードする。これをビルドするには、同じディレクトリに先に生成したスタブのディレクトリbaselineが必要なので、GeneratedStubExample.cppを/home/yhmtmt/workにコピーしてg++でビルドする。
yhmtmt@ubuntu1604: /home/yhmtmt/work $ cp ../simple-binary-encoding-1.12.2/sbe-samples/src/main/cpp/GeneratedStubExample.cpp ./
yhmtmt@ubuntu1604: /home/yhmtmt/work $ g++ GeneratedStubExample.cpp -o GeneratedStubExample
g++のバージョンによっては-std=c++11が必要かもしれない。実行すると次の通りである。
yhmtmt@ubuntu1604:/home/yhmtmt/work $ ./GeneratedStubExample
Encoded Lengths are 8 + 205
messageHeader.blockLength=49
messageHeader.templateId=1
messageHeader.schemaId=1
messageHeader.schemaVersion=0
messageHeader.encodedLength=8
car.serialNumberId=1
car.modelYearId=2
car.availableId=3
car.codeId=4
car.someNumbersId=5
car.vehicleCodeId=6
car.extrasId=7
car.engineId=9
car.fuelFiguresId=10
car.fuelFigures.speedId=11
car.fuelFigures.mpgId=12
car.fuelFigures.usageDescriptionId=200
car.performanceFiguresId=13
car.performanceFigures.octaneRatingId=14
car.performanceFigures.accelerationId=15
car.performanceFigures.acceleration.mphId=16
car.performanceFigures.acceleration.secondsId=17
car.manufacturerId=18
car.manufacturerCharacterEncoding=UTF-8
car.modelId=19
car.modelCharacterEncoding=UTF-8
car.activationCodeId=20
car.activationCodeCharacterEncoding=UTF-8
car.serialNumber=1234
car.modelYear=2013
car.available=T
car.code=A
car.someNumbers=0, 1, 2, 3, 4
car.vehicleCode=a, b, c, d, e, f
car.extras.cruiseControl=true
car.extras.sportsPack=true
car.extras.sunRoof=false
car.discountedModel=C
car.engine.capacity=2000
car.engine.numCylinders=4
car.engine.maxRpm=9000
car.engine.manufacturerCodeLength=3
car.engine.manufacturerCode=1, 2, 3
car.engine.fuelLength=6
car.engine.fuel=Petrol
car.engine.booster.boostType=NITROUS
car.engine.booster.horsePower=200
car.fuelFigures.speed=30
car.fuelFigures.mpg=35.9
car.fuelFigures.usageDescriptionLength=11
car.fuelFigures.usageDescription=Urban Cycle
car.fuelFigures.speed=55
car.fuelFigures.mpg=49.0
car.fuelFigures.usageDescriptionLength=14
car.fuelFigures.usageDescription=Combined Cycle
car.fuelFigures.speed=75
car.fuelFigures.mpg=40.0
car.fuelFigures.usageDescriptionLength=13
car.fuelFigures.usageDescription=Highway Cycle
car.performanceFigures.octaneRating=95
car.performanceFigures.acceleration.mph=30
car.performanceFigures.acceleration.seconds=4.0
car.performanceFigures.acceleration.mph=60
car.performanceFigures.acceleration.seconds=7.5
car.performanceFigures.acceleration.mph=100
car.performanceFigures.acceleration.seconds=12.2
car.performanceFigures.octaneRating=99
car.performanceFigures.acceleration.mph=30
car.performanceFigures.acceleration.seconds=3.8
car.performanceFigures.acceleration.mph=60
car.performanceFigures.acceleration.seconds=7.1
car.performanceFigures.acceleration.mph=100
car.performanceFigures.acceleration.seconds=11.8
car.manufacturerLength=5
car.manufacturer=Honda
car.modelLength=9
car.model=Civic VTi
car.activationCodeLength=8
car.activationCode=deadbeef
car.encodedLength=205
Decoded Lengths are 8 + 205
このサンプルは車のデータに関している。スキーマの定義ファイルとそれに対応して生成されるエンコーダ・デコーダ、そしてこのサンプルのソースを見れば応用方法は容易に理解できるはず。
#あとがき
SBEのREADMEとWikiだけではややわかりにくかった。READMEの「C++ build using CMake」の通りにやれば、cmakeからのctestの実行はできるのだが、google testを使ったことがないので、結局、自分のコードにエンコーダ・デコーダを組み込もうと思ったときにどうしたらよいのかわからなかった。仕方がないので、CMakeの生成物を追いかけて解決した。
それと、XilinxのVivadoとXSDK(2018.3)のarm-linux-gnueabihf-g++でもそのままで問題なくビルドできた。今のところ、SBEのエンコーダ・デコーダは標準ライブラリ以外に依存していないように見える。つまり、x64環境で生成したエンコーダ・デコーダを、別のビルド環境に持ち込んでも使えると思われる。