利用 Protocol Buffers 的反射(Reflection)和相关机制能够实现一些灵活的功能。
通过 message 名称构建 message 对象
Protocol buffers 提供了一套通过名字来创建 message 对象的方法:
- 获取 MessageFactory 对象
MessageFactory 类提供了一个 generated_factory() 的静态函数,此静态函数可以获取一个 MessageFactory 对象,此 MessageFactory 对象能够用来创建被编译入程序的所有的 message 对象。注意,此 Factory 是一个 Singleton,因此重复多次调用 generated_factory 函数不会创建多个 MessageFactory 对象,另外调用者也不能通过调用 delete 删除此对象。 - 获取 DescriptorPool 对象
通过 DescriptorPool 类的 generated_pool() 静态函数能够获取 DescriptorPool 的指针。此 DescriptorPool 中包含了被编译入程序的 message 的 descriptor。generated_pool 类似于 generated_factory 函数,可以被重复调用多次而不会创建多个 DescriptorPool 对象。 - 获取 message descriptor
有了 DescriptorPool 对象就可以获取到 message 的 descriptor 了。常见的一个函数是 const Descriptor * FindMessageTypeByName(const string & name) const,此函数可以通过 message 名字获取到顶层 message 的 descriptor。当然除此之外还有一些 API 可以用来获取 message discriptor,可以参考相关文档,这里就不一一详述了。 - 获取 message prototype 并构建 message 对象
前面已经讲述了获取 MessageFactory 对象的方法,有了 MessageFactory 对象就可以通过函数 MessageFactory::GetPrototype(const Descriptor * type) 获取 message prototype(实质上就是一个 message 对象)。通过调用 message prototype 的 New 函数则可以构造此类型的 message。
对同一个 Descriptor 多次调用 MessageFactory::GetPrototype 函数将返回同一个对象。通过调用 prototype 的 New 函数构造的 message 对象必须在 MessageFactory 销毁前销毁。
具体编码如下:
- #include <google/protobuf/message.h>
- #include <google/protobuf/descriptor.h>
- // 这样使用:
- // createMessageByName("tutorial.AddressBook");
- // 这里的 tutorial 为 package 名,AddressBook 为 message 名
- google::protobuf::Message* createMessageByName(const std::string& name)
- {
- const google::protobuf::Descriptor* descriptor
- = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(name);
- if (!descriptor)
- return NULL;
- const google::protobuf::Message* prototype
- = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
- if (!prototype)
- return NULL;
- return prototype->New();
- }
遍历被编译的 message
遍历某个 .proto 文件中的所有的顶层 message 可以通过直接解析 .proto 文件来完成(利用 google::protobuf::compiler 相关接口)。如果这个 .proto 文件被编译的 C++ 文件被编译进了程序,那么则无需解析 .proto 文件:
- #include <google/protobuf/message.h>
- #include <google/protobuf/descriptor.h>
- void IterateProtoFile(const std::string& name)
- {
- // 在 DescriptorPool 中通过 .proto 文件名获取到 FileDescriptor
- // FileDescriptor 对象描述了整个 .proto 文件
- const google::protobuf::FileDescriptor* fileDescriptor
- = google::protobuf::DescriptorPool::generated_pool()->FindFileByName(name);
- if (!fileDescriptor)
- return;
- // 遍历整个 .proto 文件中定义的顶层 message
- for (int i=0; i<fileDescriptor->message_type_count(); ++i)
- {
- // message_type 函数参数取值范围在 0 <= index < message_type_count()
- // 索引值 i 对应 message 在 .proto 文件中的定义的顺序
- const google::protobuf::Descriptor* descriptor = fileDescriptor->message_type(i);
- // ...
- }
- }
一个值得注意的问题是,(我所用到的)反射的 API 需要在 main 函数执行之后调用,例如:
- class Test()
- {
- public:
- Test() {
- const google::protobuf::FileDescriptor* fileDescriptor
- = google::protobuf::DescriptorPool::generated_pool()->FindFileByName("name");
- }
- };
- Test g_test;
此例中 fileDescriptor 可能为 NULL。