Protocol Buffer (proto3)を使ったC++とPython間通信
Protocol Buffer (proto3)と使ったC++プログラムとPythonスクリプト間のデータのやり取り
Protocol Buffer
Protocol BufferはGoogleが開発したプラットフォーム非依存、拡張性の高いシリアライズデータ構造です。データをバイナリで扱うため、XMLやjsonのようなテキストベースのデータ構造よりも効率が高く、Pythonのstruct.pack/unpack
よりも拡張性に優れているのが特徴です。
テスト環境
- ホスト … Windows 10 64bit, Msys2
- Python … Python 3.8.2
- C++ … clang version 9.0.1
- Protocol Buffer … Protobuf 3.11.4
コンパイラインストール
Protocol Bufferはメッセージを定義した.proto
ファイルを各種言語、プラットフォーム向けにコンパイルして使用します。Msys2環境ではpacman
でコンパイラのインストールが可能です
% pacman -S mingw-w64-x86_64-protobuf mingw-w64-x86_64-python-protobuf
% protoc --version
libprotoc 3.11.4
メッセージ定義
今回はRaspberry Pi Zeroとの間で温度計データを送受信することを想定して、以下のようなメッセージを定義しました。データ型としては、一般的な整数、浮動小数点のほか、ブールや文字列、列挙型やサブメッセージなどが使用できます。詳細な仕様は公式ページを参照してください
syntax = "proto3";
message SensorData {
string name = 1; // Sensor Name
uint64 uid = 2; // Sensor Unique ID
enum SensorType {
Unknown = 0;
Thermometer = 1;
Humidity = 2;
}
SensorType type = 3; // Sensor Type
uint64 timestamp_us = 4; // POSIX Timestamp in us
int32 index = 5; // Data index
repeated double value = 6;
}
コンパイル
定義したメッセージファイル (proto/sensor_data.proto
)をprotoc
でコンパイルして、C++とPython用のメッセージファイルを生成します
% protoc -I proto --cpp_out=generated --python_out=generated proto/sensor_data.proto
% ls genereted
sensor_data.pb.cc sensor_data.pb.h sensor_data_pb2.py
メッセージの作成
Python
PythonではSerializeToString()
でメッセージをバイト列に変換します (Stringとありますが、文字列ではなくバイトデータです)。今回は簡易テストのためファイルに書き出していますが、そのままUDPデータとして送信することも当然可能です
import os
import sys
import time
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "generated"))
import sensor_data_pb2
def main():
# データ書き出しファイル
fo = open('python_sensor.data', 'wb') # バイナリ形式でオープン
# ダミーセンサデータ作成
dummy_data = sensor_data_pb2.SensorData()
dummy_data.name = "Dummy PyThermo"
dummy_data.uid = 2
dummy_data.type = sensor_data_pb2.SensorData.SensorType.Thermometer
dummy_data.timestamp_us = int(time.time_ns() / 1000)
dummy_data.index = 1
dummy_data.value.extend([23.2, 23.3, 23.5])
fo.write(dummy_data.SerializeToString())
fo.close()
if __name__ == "__main__":
main()
import sensor_data_pb2
で、protoc
により生成されたsensor_data_pb2.py
をインポートしています。メッセージのメンバにはdata.<member_name>
のようにアクセスでき、repeated
メンバについてはlist
型と同じようにappend()
やextend()
でデータの追加が行えます
C++
C++の場合は生成されたsensor_data_pb2.h
に定義されたset_<member_name>()
というメンバ関数でデータを設定します。repeated
メンバへのデータ追加はadd_<member_name>()
関数です。シリアライズはSerializeToOstream()
関数で行います
#include <cstdint>
#include <ctime>
#include <fstream>
#include <google/protobuf/util/json_util.h>
#include "../generated/sensor_data.pb.h"
using namespace std;
int main() {
// データ書き出しファイル
fstream output("cpp_sensor.data", ios::out | ios::binary | ios::trunc);
// ダミーセンサデータ作成
SensorData dummy_data;
dummy_data.set_name("Dummy CppThermo");
dummy_data.set_uid(3);
dummy_data.set_type(SensorData_SensorType_Thermometer);
dummy_data.set_timestamp_us(static_cast<uint64_t>(time(nullptr)));
dummy_data.set_index(1);
dummy_data.add_value(15.1);
dummy_data.add_value(14.2);
dummy_data.SerializeToOstream(&output);
output.close();
return 0;
}
コンパイルにはprotoc
で生成したsensor_data.pb.cc
とともに-lprotobuf
オプションを追加してコンパイルします
% clang++ cpp/protobuf_example_wr.cc generated/sensor_data.pb.cc -lprotobuf -o protobuf_example_wr
メッセージの読み込み
続いて先ほど書き出したファイルをPythonでC++で作成したcpp_sensor.data
を、C++でPythonで作成したpython_sensor.data
を読み込みます
Python
読み出しはParseFromString()
です
import os
from pprint import pprint
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "generated"))
import sensor_data_pb2
def main():
# データ読み出し
cpp_data = sensor_data_pb2.SensorData()
fi = open('cpp_sensor.data', 'rb') # バイナリ形式でオープン
cpp_data.ParseFromString(fi.read())
pprint(cpp_data)
fi.close()
if __name__ == "__main__":
main()
% python protobuf_example_rd.py
name: "Dummy CppThermo"
uid: 3
type: Thermometer
timestamp_us: 1582651238
index: 1
value: 15.1
value: 14.2
C++
#include <fstream>
#include <iostream>
#include <google/protobuf/util/json_util.h>
#include "../generated/sensor_data.pb.h"
using namespace std;
int main() {
// データ読み出し
fstream input("python_sensor.data", ios::in | ios::binary);
SensorData python_data;
python_data.ParseFromIstream(&input);
string s;
google::protobuf::util::MessageToJsonString(python_data, &s);
cout << s << endl;
return 0;
}
% clang++ cpp/protobuf_example_rd.cc generated/sensor_data.pb.cc -lprotobuf -o protobuf_example_rd
% ./protobuf_example_rd
{"name":"Dummy PyThermo","uid":"2","type":"Thermometer","timestampUs":"1586255419234748","index":1,"value":[23.2,23.3,23.5]}
最近のコメント