protobuf反射详解

本文主要介绍protobuf里的反射功能,使用的pb版本为2.6.1,同时为了简洁,对repeated/extension字段的处理方法没有说明。

最初是起源于这样一个问题:
给定一个pb对象,如何自动遍历该对象的所有字段?

即是否有一个通用的方法可以遍历任意pb对象的所有字段,而不用关心具体对象类型。 
使用场景上有很多:
比如json格式字符串的相互转换,或者bigtable里根据pb对象的字段自动写列名和对应的value。

例如定义了pb messge类型Person如下:

 
  1. Person person;
  2. person.set_name("yingshin");
  3. person.set_age(21);

能否将该对象自动转化为json字符串{"name":"yingshin","age":21},或者自动的写hbase里的多列:

keycolumn-namecolumn-value
uidnameyingshin
uidage21

如果设置了新的字段,比如person.set_email("izualzhy@163.com"),则自动添加新的一列:

keycolumn-namecolumn-value
uidemailizualzhy@163.com

答案就是 pb的反射功能。

我们的目标是提供这样两个接口:

 
  1. //从给定的message对象序列化为固定格式的字符串
  2. void serialize_message(const google::protobuf::Message& message, std::string* serialized_string);
  3. //从给定的字符串按照固定格式还原为原message对象
  4. void parse_message(const std::string& serialized_string, google::protobuf::Message* message);

接下来逐步介绍下如何实现。

1. 反射相关接口

要介绍pb的反射功能,先看一个相关的UML示例图:

 

各个类以及接口说明:

1.1 Message

Person是自定义的pb类型,继承自Message. MessageLite作为Message基类,更加轻量级一些。
通过Message的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor/Reflection。

1.2 Descriptor

Descriptor是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto文件内容等。
本文中我们主要关注跟字段描述相关的接口,例如:

  1. 获取所有字段的个数:int field_count() const
  2. 获取单个字段描述类型FieldDescriptor的接口有很多个,例如
 
  1. const FieldDescriptor* field(int index) const;//根据定义顺序索引获取
  2. const FieldDescriptor* FindFieldByNumber(int number) const;//根据tag值获取
  3. const FieldDescriptor* FindFieldByName(const string& name) const;//根据field name获取

1.3 FieldDescriptor

FieldDescriptor描述message中的单个字段,例如字段名,字段属性(optional/required/repeated)等。
对于proto定义里的每种类型,都有一种对应的C++类型,例如:

 
  1. enum CppType {
  2. CPPTYPE_INT32 = 1, //TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32
  3. }

获取类型的label属性:

 
  1. enum Label {
  2. LABEL_OPTIONAL = 1, //optional
  3. LABEL_REQUIRED = 2, //required
  4. LABEL_REPEATED = 3, //repeated
  5.  
  6. MAX_LABEL = 3, //Constant useful for defining lookup tables indexed by Label.
  7. }

获取字段的名称:const string& name() const;

1.4 Reflection

Reflection主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成。
对每种类型,Reflection都提供了一个单独的接口用于读写字段对应的值。

例如对于读操作:

 
  1. virtual int32 GetInt32 (const Message& message,
  2. const FieldDescriptor* field) const = 0;
  3. virtual int64 GetInt64 (const Message& message,
  4. const FieldDescriptor* field) const = 0;

特殊的,对于枚举和嵌套的message:

 
  1. virtual const EnumValueDescriptor* GetEnum(
  2. const Message& message, const FieldDescriptor* field) const = 0;
  3. // See MutableMessage() for the meaning of the "factory" parameter.
  4. virtual const Message& GetMessage(const Message& message,
  5. const FieldDescriptor* field,
  6. MessageFactory* factory = NULL) const = 0;

对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum等。

2. 反射示例

示例主要是接收任意类型的message对象,遍历解析其中的每个字段、以及对应的值,按照自定义的格式存储到一个string中。同时重新反序列化该string,读取字段以及value,填充到message对象中。例如:

其中Person是自定义的protobuf message类型,用于设置一些字段验证我们的程序。
单纯的序列化/反序列化功能可以通过pb自带的SerializeToString/ParseFromString接口完成。这里主要是为了同时展示自动从pb对象里提取field/value,自动根据field/value来还原pb对象这个功能。

 
  1. int main() {
  2. std::string serialized_string;
  3. {
  4. tutorial::Person person;
  5. person.set_name("yingshin");
  6. person.set_id(123456789);
  7. person.set_email("zhy198606@gmail.com");
  8. ::tutorial::Person_PhoneNumber* phone = person.mutable_phone();
  9. phone->set_type(tutorial::Person::WORK);
  10. phone->set_number("13266666666");
  11.  
  12. serialize_message(person, &serialized_string);
  13. }
  14.  
  15. {
  16. tutorial::Person person;
  17. parse_message(serialized_string, &person);
  18. }
  19.  
  20. return 0;
  21. }

其中Person定义是对example里的addressbook.proto做了少许修改(修改的原因是本文没有涉及pb里数组的处理)

 
  1. package tutorial;
  2.  
  3. message Person {
  4. required string name = 1;
  5. required int32 id = 2; // Unique ID number for this person.
  6. optional string email = 3;
  7.  
  8. enum PhoneType {
  9. MOBILE = 0;
  10. HOME = 1;
  11. WORK = 2;
  12. }
  13.  
  14. message PhoneNumber {
  15. required string number = 1;
  16. optional PhoneType type = 2 [default = HOME];
  17. }
  18.  
  19. optional PhoneNumber phone = 4;
  20. }

3. 反射实例实现

3.1 serialize_message

serialize_message遍历提取message中各个字段以及对应的值,序列化到string中。
主要思路就是通过Descriptor得到每个字段的描述符:字段名、字段的cpp类型。
通过Reflection的GetX接口获取对应的value。

 
  1. void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {
  2. const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
  3. const google::protobuf::Reflection* reflection = message.GetReflection();
  4.  
  5. for (int i = 0; i < descriptor->field_count(); ++i) {
  6. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
  7. bool has_field = reflection->HasField(message, field);
  8.  
  9. if (has_field) {
  10. //arrays not supported
  11. assert(!field->is_repeated());
  12.  
  13. switch (field->cpp_type()) {
  14. #define CASE_FIELD_TYPE(cpptype, method, valuetype)\
  15. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
  16. valuetype value = reflection->Get##method(message, field);\
  17. int wsize = field->name().size();\
  18. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
  19. serialized_string->append(field->name().c_str(), field->name().size());\
  20. wsize = sizeof(value);\
  21. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
  22. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\
  23. break;\
  24. }
  25.  
  26. CASE_FIELD_TYPE(INT32, Int32, int);
  27. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
  28. CASE_FIELD_TYPE(FLOAT, Float, float);
  29. CASE_FIELD_TYPE(DOUBLE, Double, double);
  30. CASE_FIELD_TYPE(BOOL, Bool, bool);
  31. CASE_FIELD_TYPE(INT64, Int64, int64_t);
  32. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
  33. #undef CASE_FIELD_TYPE
  34. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
  35. int value = reflection->GetEnum(message, field)->number();
  36. int wsize = field->name().size();
  37. //写入name占用字节数
  38. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  39. //写入name
  40. serialized_string->append(field->name().c_str(), field->name().size());
  41. wsize = sizeof(value);
  42. //写入value占用字节数
  43. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  44. //写入value
  45. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
  46. break;
  47. }
  48. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
  49. std::string value = reflection->GetString(message, field);
  50. int wsize = field->name().size();
  51. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  52. serialized_string->append(field->name().c_str(), field->name().size());
  53. wsize = value.size();
  54. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  55. serialized_string->append(value.c_str(), value.size());
  56. break;
  57. }
  58. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
  59. std::string value;
  60. int wsize = field->name().size();
  61. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  62. serialized_string->append(field->name().c_str(), field->name().size());
  63. const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
  64. serialize_message(submessage, &value);
  65. wsize = value.size();
  66. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  67. serialized_string->append(value.c_str(), value.size());
  68. break;
  69. }
  70. }
  71. }
  72. }
  73. }

3.2 parse_message

parse_message通过读取field/value,还原message对象。
主要思路跟serialize_message很像,通过Descriptor得到每个字段的描述符FieldDescriptor,通过Reflection的SetX填充message。

 
  1. void parse_message(const std::string& serialized_string, google::protobuf::Message* message) {
  2. const google::protobuf::Descriptor* descriptor = message->GetDescriptor();
  3. const google::protobuf::Reflection* reflection = message->GetReflection();
  4. std::map<std::string, const google::protobuf::FieldDescriptor*> field_map;
  5.  
  6. for (int i = 0; i < descriptor->field_count(); ++i) {
  7. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
  8. field_map[field->name()] = field;
  9. }
  10.  
  11. const google::protobuf::FieldDescriptor* field = NULL;
  12. size_t pos = 0;
  13. while (pos < serialized_string.size()) {
  14. int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
  15. pos += sizeof(int);
  16. std::string name = serialized_string.substr(pos, name_size);
  17. pos += name_size;
  18.  
  19. int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
  20. pos += sizeof(int);
  21. std::string value = serialized_string.substr(pos, value_size);
  22. pos += value_size;
  23.  
  24. std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =
  25. field_map.find(name);
  26. if (iter == field_map.end()) {
  27. fprintf(stderr, "no field found.\n");
  28. continue;
  29. } else {
  30. field = iter->second;
  31. }
  32.  
  33. assert(!field->is_repeated());
  34. switch (field->cpp_type()) {
  35. #define CASE_FIELD_TYPE(cpptype, method, valuetype)\
  36. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {\
  37. reflection->Set##method(\
  38. message,\
  39. field,\
  40. *(reinterpret_cast<const valuetype*>(value.c_str())));\
  41. std::cout << field->name() << "\t" << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;\
  42. break;\
  43. }
  44. CASE_FIELD_TYPE(INT32, Int32, int);
  45. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
  46. CASE_FIELD_TYPE(FLOAT, Float, float);
  47. CASE_FIELD_TYPE(DOUBLE, Double, double);
  48. CASE_FIELD_TYPE(BOOL, Bool, bool);
  49. CASE_FIELD_TYPE(INT64, Int64, int64_t);
  50. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
  51. #undef CASE_FIELD_TYPE
  52. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
  53. const google::protobuf::EnumValueDescriptor* enum_value_descriptor =
  54. field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));
  55. reflection->SetEnum(message, field, enum_value_descriptor);
  56. std::cout << field->name() << "\t" << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;
  57. break;
  58. }
  59. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
  60. reflection->SetString(message, field, value);
  61. std::cout << field->name() << "\t" << value << std::endl;
  62. break;
  63. }
  64. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
  65. google::protobuf::Message* submessage = reflection->MutableMessage(message, field);
  66. parse_message(value, submessage);
  67. break;
  68. }
  69. default: {
  70. break;
  71. }
  72. }
  73. }
  74. }
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值