protobuf反射詳解(c++)

本文主要介紹protobuf反射的功能,其中部分內(nèi)容轉(zhuǎn)載至https://blog.csdn.net/chary8088/article/details/52876674,repeated 內(nèi)容為原創(chuàng)。

最初是起源于這樣一個(gè)問題:
給定一個(gè)pb對(duì)象,如何自動(dòng)遍歷該對(duì)象的所有字段?

即是否有一個(gè)通用的方法可以遍歷任意pb對(duì)象的所有字段,而不用關(guān)心具體對(duì)象類型。
使用場(chǎng)景上有很多:
比如json格式字符串的相互轉(zhuǎn)換,或者bigtable里根據(jù)pb對(duì)象的字段自動(dòng)寫列名和對(duì)應(yīng)的value。

例如定義了pb messge類型Person如下:

Person person;
person.set_name("yingshin");
person.set_age(21);

能否將該對(duì)象自動(dòng)轉(zhuǎn)化為json字符串{"name":"yingshin","age":21}?

答案就是pb的反射功能

我們的目標(biāo)是提供這樣兩個(gè)接口:

//從給定的message對(duì)象序列化為固定格式的字符串
void serialize_message(const google::protobuf::Message& message, std::string* serialized_string);
//從給定的字符串按照固定格式還原為原message對(duì)象
void parse_message(const std::string& serialized_string, google::protobuf::Message* message);

各個(gè)類以及接口說明:

1.1 Message
Person是自定義的pb類型,繼承自Message. MessageLite作為Message基類,更加輕量級(jí)一些。
通過Message的兩個(gè)接口GetDescriptor/GetReflection,可以獲取該類型對(duì)應(yīng)的Descriptor/Reflection。

1.2 Descriptor
Descriptor是對(duì)message類型定義的描述,包括message的名字、所有字段的描述、原始的proto文件內(nèi)容等。
本文中我們主要關(guān)注跟字段描述相關(guān)的接口,例如:

獲取所有字段的個(gè)數(shù):int field_count() const
獲取單個(gè)字段描述類型FieldDescriptor的接口有很多個(gè),例如
  
const FieldDescriptor* field(int index) const;//根據(jù)定義順序索引獲取
const FieldDescriptor* FindFieldByNumber(int number) const;//根據(jù)tag值獲取
const FieldDescriptor* FindFieldByName(const string& name) const;//根據(jù)field name獲取

1.3 FieldDescriptor
FieldDescriptor描述message中的單個(gè)字段,例如字段名,字段屬性(optional/required/repeated)等。
對(duì)于proto定義里的每種類型,都有一種對(duì)應(yīng)的C++類型,例如:

  
enum CppType {
    CPPTYPE_INT32 = 1, //TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32
}
獲取類型的label屬性:

  
enum Label {
    LABEL_OPTIONAL = 1, //optional
    LABEL_REQUIRED = 2, //required
    LABEL_REPEATED = 3, //repeated
    MAX_LABEL = 3, //Constant useful for defining lookup tables indexed by Label.
}
獲取字段的名稱:const string& name() const;。

1.4 Reflection
Reflection主要提供了動(dòng)態(tài)讀寫pb字段的接口,對(duì)pb對(duì)象的自動(dòng)讀寫主要通過該類完成。
對(duì)每種類型,Reflection都提供了一個(gè)單獨(dú)的接口用于讀寫字段對(duì)應(yīng)的值。

例如對(duì)于讀操作:

  
  virtual int32  GetInt32 (const Message& message,
                           const FieldDescriptor* field) const = 0;
  virtual int64  GetInt64 (const Message& message,
                           const FieldDescriptor* field) const = 0;
特殊的,對(duì)于枚舉和嵌套的message:

  
  virtual const EnumValueDescriptor* GetEnum(
      const Message& message, const FieldDescriptor* field) const = 0;
  // See MutableMessage() for the meaning of the "factory" parameter.
  virtual const Message& GetMessage(const Message& message,
                                    const FieldDescriptor* field,
                                    MessageFactory* factory = NULL) const = 0;
對(duì)于寫操作也是類似的接口,例如SetInt32/SetInt64/SetEnum等。
  1. 反射示例
    示例主要是接收任意類型的message對(duì)象,遍歷解析其中的每個(gè)字段、以及對(duì)應(yīng)的值,按照自定義的格式存儲(chǔ)到一個(gè)string中。同時(shí)重新反序列化該string,讀取字段以及value,填充到message對(duì)象中。例如:

其中Person是自定義的protobuf message類型,用于設(shè)置一些字段驗(yàn)證我們的程序。
單純的序列化/反序列化功能可以通過pb自帶的SerializeToString/ParseFromString接口完成。這里主要是為了同時(shí)展示自動(dòng)從pb對(duì)象里提取field/value,自動(dòng)根據(jù)field/value來還原pb對(duì)象這個(gè)功能。

int main() {
  std::string serialized_string;
  {
      tutorial::Person person;
      person.set_name("yingshin");
      person.set_id(123456789);
      person.set_email("zhy198606@gmail.com");
      ::tutorial::Person_PhoneNumber* phone = person.mutable_phone();
      phone->set_type(tutorial::Person::WORK);
      phone->set_number("13266666666");
      serialize_message(person, &serialized_string);
  }
  {
      tutorial::Person person;
      parse_message(serialized_string, &person);
  }
  return 0;
}

其中Person定義是對(duì)example里的addressbook.proto做了少許修改(修改的原因是本文沒有涉及pb里數(shù)組的處理)

package tutorial;
message Person {
required string name = 1;
required int32 id = 2;        // Unique ID number for this person.
optional string email = 3;
enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
}
message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
}
optional PhoneNumber phone = 4;
}
  1. 反射實(shí)例實(shí)現(xiàn)
    3.1 serialize_message
    serialize_message遍歷提取message中各個(gè)字段以及對(duì)應(yīng)的值,序列化到string中。
    主要思路就是通過Descriptor得到每個(gè)字段的描述符:字段名、字段的cpp類型。
    通過Reflection的GetX接口獲取對(duì)應(yīng)的value。
void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {
  const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
  const google::protobuf::Reflection* reflection = message.GetReflection();
  for (int i = 0; i < descriptor->field_count(); ++i) {
      const google::protobuf::FieldDescriptor* field = descriptor->field(i);
      bool has_field = reflection->HasField(message, field);
      if (has_field) {
          //arrays not supported
          assert(!field->is_repeated());
          switch (field->cpp_type()) {
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\
              case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
                  valuetype value = reflection->Get##method(message, field);\
                  int wsize = field->name().size();\
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
                  serialized_string->append(field->name().c_str(), field->name().size());\
                  wsize = sizeof(value);\
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
                  serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\
                  break;\
              }
              CASE_FIELD_TYPE(INT32, Int32, int);
              CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
              CASE_FIELD_TYPE(FLOAT, Float, float);
              CASE_FIELD_TYPE(DOUBLE, Double, double);
              CASE_FIELD_TYPE(BOOL, Bool, bool);
              CASE_FIELD_TYPE(INT64, Int64, int64_t);
              CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_FIELD_TYPE
              case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
                  int value = reflection->GetEnum(message, field)->number();
                  int wsize = field->name().size();
                  //寫入name占用字節(jié)數(shù)
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                  //寫入name
                  serialized_string->append(field->name().c_str(), field->name().size());
                  wsize = sizeof(value);
                  //寫入value占用字節(jié)數(shù)
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                  //寫入value
                  serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
                  break;
              }
              case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
                  std::string value = reflection->GetString(message, field);
                  int wsize = field->name().size();
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                  serialized_string->append(field->name().c_str(), field->name().size());
                  wsize = value.size();
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                  serialized_string->append(value.c_str(), value.size());
                  break;
              }
              case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                  std::string value;
                  int wsize = field->name().size();
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                  serialized_string->append(field->name().c_str(), field->name().size());
                  const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
                  serialize_message(submessage, &value);
                  wsize = value.size();
                  serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                  serialized_string->append(value.c_str(), value.size());
                  break;
              }
          }
      }
  }
}

3.2 parse_message
parse_message通過讀取field/value,還原message對(duì)象。
主要思路跟serialize_message很像,通過Descriptor得到每個(gè)字段的描述符FieldDescriptor,通過Reflection的SetX填充message。

void parse_message(const std::string& serialized_string, google::protobuf::Message* message) {
  const google::protobuf::Descriptor* descriptor = message->GetDescriptor();
  const google::protobuf::Reflection* reflection = message->GetReflection();
  std::map<std::string, const google::protobuf::FieldDescriptor*> field_map;
  for (int i = 0; i < descriptor->field_count(); ++i) {
      const google::protobuf::FieldDescriptor* field = descriptor->field(i);
      field_map[field->name()] = field;
  }
  const google::protobuf::FieldDescriptor* field = NULL;
  size_t pos = 0;
  while (pos < serialized_string.size()) {
      int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
      pos += sizeof(int);
      std::string name = serialized_string.substr(pos, name_size);
      pos += name_size;
      int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
      pos += sizeof(int);
      std::string value = serialized_string.substr(pos, value_size);
      pos += value_size;
      std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =
          field_map.find(name);
      if (iter == field_map.end()) {
          fprintf(stderr, "no field found.\n");
          continue;
      } else {
          field = iter->second;
      }
      assert(!field->is_repeated());
      switch (field->cpp_type()) {
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\
          case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {\
              reflection->Set##method(\
                      message,\
                      field,\
                      *(reinterpret_cast<const valuetype*>(value.c_str())));\
              std::cout << field->name() << "\t" << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;\
              break;\
          }
          CASE_FIELD_TYPE(INT32, Int32, int);
          CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
          CASE_FIELD_TYPE(FLOAT, Float, float);
          CASE_FIELD_TYPE(DOUBLE, Double, double);
          CASE_FIELD_TYPE(BOOL, Bool, bool);
          CASE_FIELD_TYPE(INT64, Int64, int64_t);
          CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_FIELD_TYPE
          case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
              const google::protobuf::EnumValueDescriptor* enum_value_descriptor =
                  field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));
              reflection->SetEnum(message, field, enum_value_descriptor);
              std::cout << field->name() << "\t" << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;
              break;
          }
          case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
              reflection->SetString(message, field, value);
              std::cout << field->name() << "\t" << value << std::endl;
              break;
          }
          case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
              google::protobuf::Message* submessage = reflection->MutableMessage(message, field);
              parse_message(value, submessage);
              break;
          }
          default: {
              break;
          }
      }
  }
}

------------------------------以下為新增內(nèi)容:
為了支持std::cout 直接調(diào)用pb序列化后的內(nèi)容,且支持repeated 類型,新的接口如下:

void serialize_message_to_str(const google::protobuf::Message &message, std::string *serialized_str) {
    const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
    const google::protobuf::Reflection* reflection = message.GetReflection();
    for (int i = 0; i < descriptor->field_count(); ++i) {
        const google::protobuf::FieldDescriptor* field = descriptor->field(i);
        if (field->is_repeated()) {
            //repeated
            int fieldSize = reflection->FieldSize(message, field);
            switch (field->cpp_type()) {
#define CASE_REPEATED_FIELD_TYPE(cpptype, method, valuetype)\
                case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
                    serialized_str->append("repedted_field:\t");\
                    serialized_str->append(field->name().c_str(), field->name().size());\
                    serialized_str->append("\t");\
                    for(int i = 0; i < fieldSize; ++i) {\
                        valuetype value = reflection->GetRepeated##method(message, field, i);\
                        serialized_str->append(std::to_string(value));\
                        serialized_str->append("\t");\
                    }\
                    break;\
                }
                CASE_REPEATED_FIELD_TYPE(INT32, Int32, int);
                CASE_REPEATED_FIELD_TYPE(UINT32, UInt32, uint32_t);
                CASE_REPEATED_FIELD_TYPE(FLOAT, Float, float);
                CASE_REPEATED_FIELD_TYPE(DOUBLE, Double, double);
                CASE_REPEATED_FIELD_TYPE(BOOL, Bool, bool);
                CASE_REPEATED_FIELD_TYPE(INT64, Int64, int64_t);
                CASE_REPEATED_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_REPEATED_FIELD_TYPE
                case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
                    serialized_str->append("repedted_field:\t");
                    serialized_str->append(field->name().c_str(), field->name().size());
                    serialized_str->append("\t");
                    for(int i = 0; i < fieldSize; ++i) {
                        std::string value = reflection->GetRepeatedString(message, field, i);
                        serialized_str->append(value.c_str(), value.size());
                        serialized_str->append("\t");
                    }
                    break;
                }
                case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
                    serialized_str->append("repedted_field:\t");
                    serialized_str->append(field->name().c_str(), field->name().size());
                    serialized_str->append("\t");
                    for(int i = 0; i < fieldSize; ++i) {
                        int value = reflection->GetRepeatedEnumValue(message, field, i);
                        serialized_str->append(std::to_string(value));
                        serialized_str->append("\t");
                    }
                    break;
                }
                case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                    serialized_str->append("repedted_field[sub_message]:\t");
                    serialized_str->append(field->name().c_str(), field->name().size());
                    serialized_str->append("\t");
                    std::string value;
                    for(int i = 0 ;i < fieldSize; ++i) {
                        const google::protobuf::Message& submessage = reflection->GetRepeatedMessage(message, field, i);
                        serialize_message_to_str(submessage, &value);
                    }
                    serialized_str->append(value.c_str(), value.size());
                    serialized_str->append("\t]");
                    break;
                }
            }
            
            continue;
        } 

        bool has_field = reflection->HasField(message, field);
        if (has_field) {
            //arrays not supported
            //assert(!field->is_repeated());
            switch (field->cpp_type()) {
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\
                case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
                    valuetype value = reflection->Get##method(message, field);\
                    serialized_str->append(field->name().c_str(), field->name().size());\
                    serialized_str->append("\t");\
                    serialized_str->append(std::to_string(value));\
                    serialized_str->append("\t");\
                    break;\
                }
                CASE_FIELD_TYPE(INT32, Int32, int);
                CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
                CASE_FIELD_TYPE(FLOAT, Float, float);
                CASE_FIELD_TYPE(DOUBLE, Double, double);
                CASE_FIELD_TYPE(BOOL, Bool, bool);
                CASE_FIELD_TYPE(INT64, Int64, int64_t);
                CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_FIELD_TYPE
                case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
                    int value = reflection->GetEnum(message, field)->number();
                    int wsize = field->name().size();
                    //寫入name
                    serialized_str->append(field->name().c_str(), field->name().size());
                    serialized_str->append("\t");
                    //寫入value
                    serialized_str->append(std::to_string(value));
                    serialized_str->append("\t");
                    break;
                }
                case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
                    std::string value = reflection->GetString(message, field);
                    serialized_str->append(field->name().c_str(), field->name().size());
                    serialized_str->append("\t");
                    serialized_str->append(value.c_str(), value.size());
                    serialized_str->append("\t");
                    break;
                }
                case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                    std::string value;
                    serialized_str->append("sub_message:\t");
                    serialized_str->append(field->name().c_str(), field->name().size());
                    serialized_str->append("\t[");
                    const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
                    serialize_message_to_str(submessage, &value);
                    serialized_str->append(value.c_str(), value.size());
                    serialized_str->append("\t]");
                    break;
                }
            }
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 翻譯查閱外網(wǎng)資料過程中遇到的比較優(yōu)秀的文章和資料,一是作為技術(shù)參考以便日后查閱,二是訓(xùn)練英文能力。此文翻譯自 Pr...
    401閱讀 46,116評(píng)論 4 30
  • 在介紹了 ProtoBuf 序列化原理之后,本文介紹 ProtoBuf 的反射技術(shù)原理。 反射技術(shù)簡(jiǎn)介 對(duì)于反射大...
    401閱讀 28,642評(píng)論 9 42
  • 目的 調(diào)研 protobuf 磁盤追加方案 Protobuf 為什么不能磁盤追加 Protobuf 把數(shù)據(jù)轉(zhuǎn)成二進(jìn)...
    杰克大王閱讀 1,071評(píng)論 0 0
  • protocolbuffer(以下簡(jiǎn)稱PB)是google 的一種數(shù)據(jù)交換的格式,它獨(dú)立于語言,獨(dú)立于平臺(tái)。goo...
    千淘萬漉閱讀 32,405評(píng)論 5 13
  • 序列化概述 當(dāng)兩個(gè)服務(wù)在進(jìn)行通信時(shí),彼此可以發(fā)送各種類型的數(shù)據(jù)。無論是何種類型的數(shù)據(jù),都會(huì)以字節(jié)序列的形式在網(wǎng)絡(luò)上...
    _張曉龍_閱讀 8,386評(píng)論 0 11

友情鏈接更多精彩內(nèi)容