Protobuf 动态加载 .proto 文件并操作 Message

Google Protocol Buffer 的常规用法需要使用 protoc.proto 编译成 .pb.h.pb.cc,这样做效率非常高,但是耦合性也很高。在某些追求通用性而不追求性能的场景下,需要使用 .proto 直接操作 protobuf 数据。

本例使用的 .proto 文件来自 https://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/adressbook.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;
    }
    
    /*
    查找 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 调用者管理
    */
    DynamicMessageFactory message_factory;
    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"), "abc@163.com");

    // 把动态设置的 Message 的数据以 JSON 格式输出
    util::JsonPrintOptions json_options;
    json_options.add_whitespace = true;
    json_options.always_print_primitive_fields = true;
    json_options.preserve_proto_field_names = true;
    std::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": "abc@163.com",
 "phones": []
}

https://developers.google.com/protocol-buffers/docs/reference/cpp

posted @ 2021-12-25 23:08  mkckr0  阅读(2064)  评论(0编辑  收藏  举报