本文主要介紹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等。
- 反射示例
示例主要是接收任意類型的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;
}
- 反射實(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;
}
}
}
}
}