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示例图:

pb-reflection

各个类以及接口说明:

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. MAX_LABEL = 3, //Constant useful for defining lookup tables indexed by Label.
  6. }

获取字段的名称: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. serialize_message(person, &serialized_string);
  12. }
  13. {
  14. tutorial::Person person;
  15. parse_message(serialized_string, &person);
  16. }
  17. return 0;
  18. }

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

  
  
  1. package tutorial;
  2. message Person {
  3. required string name = 1;
  4. required int32 id = 2; // Unique ID number for this person.
  5. optional string email = 3;
  6. enum PhoneType {
  7. MOBILE = 0;
  8. HOME = 1;
  9. WORK = 2;
  10. }
  11. message PhoneNumber {
  12. required string number = 1;
  13. optional PhoneType type = 2 [default = HOME];
  14. }
  15. optional PhoneNumber phone = 4;
  16. }
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. for (int i = 0; i < descriptor->field_count(); ++i) {
  5. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
  6. bool has_field = reflection->HasField(message, field);
  7. if (has_field) {
  8. //arrays not supported
  9. assert(!field->is_repeated());
  10. switch (field->cpp_type()) {
  11. #define CASE_FIELD_TYPE(cpptype, method, valuetype)\
  12. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
  13. valuetype value = reflection->Get##method(message, field);\
  14. int wsize = field->name().size();\
  15. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
  16. serialized_string->append(field->name().c_str(), field->name().size());\
  17. wsize = sizeof(value);\
  18. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
  19. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\
  20. break;\
  21. }
  22. CASE_FIELD_TYPE(INT32, Int32, int);
  23. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
  24. CASE_FIELD_TYPE(FLOAT, Float, float);
  25. CASE_FIELD_TYPE(DOUBLE, Double, double);
  26. CASE_FIELD_TYPE(BOOL, Bool, bool);
  27. CASE_FIELD_TYPE(INT64, Int64, int64_t);
  28. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
  29. #undef CASE_FIELD_TYPE
  30. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
  31. int value = reflection->GetEnum(message, field)->number();
  32. int wsize = field->name().size();
  33. //写入name占用字节数
  34. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  35. //写入name
  36. serialized_string->append(field->name().c_str(), field->name().size());
  37. wsize = sizeof(value);
  38. //写入value占用字节数
  39. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  40. //写入value
  41. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
  42. break;
  43. }
  44. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
  45. std::string value = reflection->GetString(message, field);
  46. int wsize = field->name().size();
  47. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  48. serialized_string->append(field->name().c_str(), field->name().size());
  49. wsize = value.size();
  50. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  51. serialized_string->append(value.c_str(), value.size());
  52. break;
  53. }
  54. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
  55. std::string value;
  56. int wsize = field->name().size();
  57. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  58. serialized_string->append(field->name().c_str(), field->name().size());
  59. const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
  60. serialize_message(submessage, &value);
  61. wsize = value.size();
  62. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  63. serialized_string->append(value.c_str(), value.size());
  64. break;
  65. }
  66. }
  67. }
  68. }
  69. }
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. for (int i = 0; i < descriptor->field_count(); ++i) {
  6. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
  7. field_map[field->name()] = field;
  8. }
  9. const google::protobuf::FieldDescriptor* field = NULL;
  10. size_t pos = 0;
  11. while (pos < serialized_string.size()) {
  12. int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
  13. pos += sizeof(int);
  14. std::string name = serialized_string.substr(pos, name_size);
  15. pos += name_size;
  16. int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
  17. pos += sizeof(int);
  18. std::string value = serialized_string.substr(pos, value_size);
  19. pos += value_size;
  20. std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =
  21. field_map.find(name);
  22. if (iter == field_map.end()) {
  23. fprintf(stderr, "no field found.\n");
  24. continue;
  25. } else {
  26. field = iter->second;
  27. }
  28. assert(!field->is_repeated());
  29. switch (field->cpp_type()) {
  30. #define CASE_FIELD_TYPE(cpptype, method, valuetype)\
  31. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {\
  32. reflection->Set##method(\
  33. message,\
  34. field,\
  35. *(reinterpret_cast<const valuetype*>(value.c_str())));\
  36. std::cout << field->name() << "\t" << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;\
  37. break;\
  38. }
  39. CASE_FIELD_TYPE(INT32, Int32, int);
  40. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
  41. CASE_FIELD_TYPE(FLOAT, Float, float);
  42. CASE_FIELD_TYPE(DOUBLE, Double, double);
  43. CASE_FIELD_TYPE(BOOL, Bool, bool);
  44. CASE_FIELD_TYPE(INT64, Int64, int64_t);
  45. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
  46. #undef CASE_FIELD_TYPE
  47. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
  48. const google::protobuf::EnumValueDescriptor* enum_value_descriptor =
  49. field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));
  50. reflection->SetEnum(message, field, enum_value_descriptor);
  51. std::cout << field->name() << "\t" << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;
  52. break;
  53. }
  54. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
  55. reflection->SetString(message, field, value);
  56. std::cout << field->name() << "\t" << value << std::endl;
  57. break;
  58. }
  59. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
  60. google::protobuf::Message* submessage = reflection->MutableMessage(message, field);
  61. parse_message(value, submessage);
  62. break;
  63. }
  64. default: {
  65. break;
  66. }
  67. }
  68. }
  69. }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值