1. 程式人生 > >Protocol Buffers 反射及相關 API

Protocol Buffers 反射及相關 API

利用 Protocol Buffers 的反射(Reflection)和相關機制能夠實現一些靈活的功能。

通過 message 名稱構建 message 物件

Protocol buffers 提供了一套通過名字來建立 message 物件的方法:

  1. 獲取 MessageFactory 物件
    MessageFactory 類提供了一個 generated_factory() 的靜態函式,此靜態函式可以獲取一個 MessageFactory 物件,此 MessageFactory 物件能夠用來建立被編譯入程式的所有的 message 物件。注意,此 Factory 是一個 Singleton,因此重複多次呼叫 generated_factory 函式不會建立多個 MessageFactory 物件,另外呼叫者也不能通過呼叫 delete 刪除此物件。
  2. 獲取 DescriptorPool 物件
    通過 DescriptorPool 類的 generated_pool() 靜態函式能夠獲取 DescriptorPool 的指標。此 DescriptorPool 中包含了被編譯入程式的 message 的 descriptor。generated_pool 類似於 generated_factory 函式,可以被重複呼叫多次而不會建立多個 DescriptorPool 物件。
  3. 獲取 message descriptor
    有了 DescriptorPool 物件就可以獲取到 message 的 descriptor 了。常見的一個函式是 const Descriptor * FindMessageTypeByName(const string & name) const,此函式可以通過 message 名字獲取到頂層 message 的 descriptor。當然除此之外還有一些 API 可以用來獲取 message discriptor,可以參考相關文件,這裡就不一一詳述了。
  4. 獲取 message prototype 並構建 message 物件
    前面已經講述了獲取 MessageFactory 物件的方法,有了 MessageFactory 物件就可以通過函式 MessageFactory::GetPrototype(const Descriptor * type) 獲取 message prototype(實質上就是一個 message 物件)。通過呼叫 message prototype 的 New 函式則可以構造此型別的 message。
    對同一個 Descriptor 多次呼叫 MessageFactory::GetPrototype 函式將返回同一個物件。通過呼叫 prototype 的 New 函式構造的 message 物件必須在 MessageFactory 銷燬前銷燬。

具體編碼如下:

  1. #include<google/protobuf/message.h>
  2. #include<google/protobuf/descriptor.h>
  3. // 這樣使用:
  4. // createMessageByName("tutorial.AddressBook");
  5. // 這裡的 tutorial 為 package 名,AddressBook 為 message 名
  6. google::protobuf::Message* createMessageByName(const std::string& name)
  7. {
  8. const google::protobuf::Descriptor* descriptor
  9. = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(name);
  10. if(!descriptor)
  11. return NULL;
  12. const google::protobuf::Message* prototype
  13. = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
  14. if(!prototype)
  15. return NULL;
  16. return prototype->New();
  17. }

遍歷被編譯的 message

遍歷某個 .proto 檔案中的所有的頂層 message 可以通過直接解析 .proto 檔案來完成(利用 google::protobuf::compiler 相關介面)。如果這個 .proto 檔案被編譯的 C++ 檔案被編譯進了程式,那麼則無需解析 .proto 檔案:

  1. #include<google/protobuf/message.h>
  2. #include<google/protobuf/descriptor.h>
  3. voidIterateProtoFile(const std::string& name)
  4. {
  5. // 在 DescriptorPool 中通過 .proto 檔名獲取到 FileDescriptor
  6. // FileDescriptor 物件描述了整個 .proto 檔案
  7. const google::protobuf::FileDescriptor* fileDescriptor
  8. = google::protobuf::DescriptorPool::generated_pool()->FindFileByName(name);
  9. if(!fileDescriptor)
  10. return;
  11. // 遍歷整個 .proto 檔案中定義的頂層 message
  12. for(int i=0; i<fileDescriptor->message_type_count();++i)
  13. {
  14. // message_type 函式引數取值範圍在 0 <= index < message_type_count()
  15. // 索引值 i 對應 message 在 .proto 檔案中的定義的順序
  16. const google::protobuf::Descriptor* descriptor = fileDescriptor->message_type(i);
  17. // ...
  18. }
  19. }

一個值得注意的問題是,(我所用到的)反射的 API 需要在 main 函式執行之後呼叫,例如:

  1. classTest()
  2. {
  3. public:
  4. Test(){
  5. const google::protobuf::FileDescriptor* fileDescriptor
  6. = google::protobuf::DescriptorPool::generated_pool()->FindFileByName("name");
  7. }
  8. };
  9. Test g_test;

此例中 fileDescriptor 可能為 NULL。