Protobuf 動態載入 .proto 文件並操作 Message
- 2021 年 12 月 25 日
- 筆記
- C++, Google Protocol Buffer, protobuf
Google Protocol Buffer 的常規用法需要使用 protoc
將 .proto
編譯成 .pb.h
和 .pb.cc
,這樣做效率非常高,但是耦合性也很高。在某些追求通用性而不追求性能的場景下,需要使用 .proto
直接操作 protobuf 數據。
本例使用的 .proto
文件來自 //developers.google.com/protocol-buffers/docs/cpptutorial ,但是把它拆成了兩個 .proto
文件
// ./proto/person.proto
syntax = "proto2";
package tutorial;
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
// ./proto/person.proto
syntax = "proto2";
package tutorial;
import "person.proto";
message AddressBook {
repeated Person people = 1;
}=
示例程式碼
#include <iostream>
#include <google/protobuf/compiler/importer.h>
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/util/json_util.h>
using namespace google::protobuf;
/*
構造 Importer 必須指定 error_collector 用於處理錯誤資訊
AddError 是純虛函數,必須 override
*/
class MyMultiFileErrorCollector : public compiler::MultiFileErrorCollector
{
virtual void AddError(const std::string& filename, int line, int column, const std::string& message) override
{
std::cout << "file: " << filename << ", line: " << line << ", col: " << column<< ", message: " << message << std::endl;
}
};
int main()
{
/*
構造 DiskSourceTree,並添加虛擬路徑。protobuf 使用 Importor 導入 .proto 文件時,會虛擬路徑進行查找
在本例中,導入 addressbook.proto 時會使用 ./proto/addressbook.proto
*/
compiler::DiskSourceTree disk_source_tree;
disk_source_tree.MapPath("", "proto");
MyMultiFileErrorCollector error_collector;
/*
導入 addressbook.proto 時,會自動導入所有依賴的 .proto 文件
在本例中,person.proto 也會被自動導入
*/
compiler::Importer importer(&disk_source_tree, &error_collector);
const FileDescriptor* file_descriptor = importer.Import("addressbook.proto");
if (!file_descriptor) {
exit(-1);
}
// 把 addressbook.proto 和 person.proto 都列印出來
std::cout << "====== all .proto files ======" << std::endl;
std::cout << file_descriptor->DebugString() << std::endl;
for (int i = 0; i < file_descriptor->dependency_count(); ++i) {
std::cout << file_descriptor->dependency(i)->DebugString() << std::endl;
}
// 構造 DynamicMessageFactory 用於動態構造 Message
DynamicMessageFactory message_factory(importer.pool()->generated_pool());
/*
查找 Person 的 Descriptor
不能使用 file_descriptor 查找,它只包含 addresssbook.proto ,只能找到 AddressBook,而 DescriptorPool 包含了所有數據
在使用 DescriptorPool 查找時需要使用全名,如:tutorial.Person
在使用 FileDescritor 查找需要使用頂級名字,如 AddressBook,而不是 tutorial.AddressBook
*/
const Descriptor* person_descriptor = importer.pool()->FindMessageTypeByName("tutorial.Person");
/*
使用工廠創建默認 Message,然後構造一個可以用來修改的 Message
這個 Message 的生命周期由 New 調用者管理
*/
const Message* default_person = message_factory.GetPrototype(person_descriptor);
Message* person = default_person->New();
// 使用 Reflection 修改 Message 的數據
const Reflection* reflection = person->GetReflection();
reflection->SetString(person, person_descriptor->FindFieldByName("name"), "abc");
reflection->SetInt32(person, person_descriptor->FindFieldByName("id"), 123456);
reflection->SetString(person, person_descriptor->FindFieldByName("email"), "[email protected]");
// 把動態設置的 Message 的數據以 JSON 格式輸出
util::JsonPrintOptions json_options;
json_options.add_whitespace = true;
json_options.preserve_proto_field_names = true;
string output;
util::MessageToJsonString(*person, &output, json_options);
std::cout << "====== Person data ======" << std::endl;
std::cout << output;
// 析構 person
delete person;
}
輸出
====== all .proto files ======
syntax = "proto2";
import "person.proto";
package tutorial;
message AddressBook {
repeated .tutorial.Person people = 1;
}
syntax = "proto2";
package tutorial;
message Person {
message PhoneNumber {
optional string number = 1;
optional .tutorial.Person.PhoneType type = 2 [default = HOME];
}
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
repeated .tutorial.Person.PhoneNumber phones = 4;
}
====== Person data ======
{
"name": "abc",
"id": 123456,
"email": "[email protected]"
}
//developers.google.com/protocol-buffers/docs/reference/cpp