所謂反射(reflection),最直觀的理解就是能夠在運行中經過一個字符串的名稱得到一個內存地址。在protobuf中,這一點經過Reflection對象完成,儘管這個類的接口
virtual int32 GetInt32 (const Message& message,
const FieldDescriptor* field) const = 0;
並非經過字符串名稱來得到,可是因爲能夠經過field的字符串名稱找到一個field的描述符,因此這個這裏說能夠經過一個字符串名稱得到一個內存地址也沒有毛病。
對於咱們使用的消息,這個具體的接口實如今protobuf-master\src\google\protobuf\generated_message_reflection.cc文件中實現,從這個文件的具體實現來看,它是經過protobuf-master\src\google\protobuf\generated_message_reflection.h一個便宜表來實現
// Offset of any field.
uint32 GetFieldOffset(const FieldDescriptor* field) const {
if (field->containing_oneof()) {
size_t offset =
static_cast<size_t>(field->containing_type()->field_count() +
field->containing_oneof()->index());
return OffsetValue(offsets_[offset], field->type());
} else {
return GetFieldOffsetNonOneof(field);
}
}
這個偏移表的來源也就是在使用protobuf生成的pb文件中包含的offsets數組
const ::PROTOBUF_NAMESPACE_ID::uint32 TableStruct_demo_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
PROTOBUF_FIELD_OFFSET(::tutorial::Person_PhoneNumber, _has_bits_),
PROTOBUF_FIELD_OFFSET(::tutorial::Person_PhoneNumber, _internal_metadata_),
……
}
而其中的宏protobuf-master\src\google\protobuf\port_def.inc
// Note that we calculate relative to the pointer value 16 here since if we
// just use zero, GCC complains about dereferencing a NULL pointer. We
// choose 16 rather than some other number just in case the compiler would
// be confused by an unaligned pointer.
#define PROTOBUF_FIELD_OFFSET(TYPE, FIELD) \
static_cast< ::google::protobuf::uint32>(reinterpret_cast<const char*>( \
&reinterpret_cast<const TYPE*>(16)->FIELD) - \
reinterpret_cast<const char*>(16))ios
下面的proto文件描述了一個proto文件中Fielde信息,protoc編譯生成的字符串類型的描述符使用的就是這種形式的描述文件
protobuf-master\src\google\protobuf\descriptor.proto
// Describes a field within a message.
message FieldDescriptorProto {
enum Type {
// 0 is reserved for errors.
// Order is weird for historical reasons.
TYPE_DOUBLE = 1;
TYPE_FLOAT = 2;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
// negative values are likely.
TYPE_INT64 = 3;
TYPE_UINT64 = 4;
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
// negative values are likely.
TYPE_INT32 = 5;
TYPE_FIXED64 = 6;
TYPE_FIXED32 = 7;
TYPE_BOOL = 8;
TYPE_STRING = 9;
// Tag-delimited aggregate.
// Group type is deprecated and not supported in proto3. However, Proto3
// implementations should still be able to parse the group wire format and
// treat group fields as unknown fields.
TYPE_GROUP = 10;
TYPE_MESSAGE = 11; // Length-delimited aggregate.c++
// New in version 2.
TYPE_BYTES = 12;
TYPE_UINT32 = 13;
TYPE_ENUM = 14;
TYPE_SFIXED32 = 15;
TYPE_SFIXED64 = 16;
TYPE_SINT32 = 17; // Uses ZigZag encoding.
TYPE_SINT64 = 18; // Uses ZigZag encoding.
};json
enum Label {
// 0 is reserved for errors
LABEL_OPTIONAL = 1;
LABEL_REQUIRED = 2;
LABEL_REPEATED = 3;
};數組
optional string name = 1;
optional int32 number = 3;
optional Label label = 4;數據結構
// If type_name is set, this need not be set. If both this and type_name
// are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
optional Type type = 5;ide
// For message and enum types, this is the name of the type. If the name
// starts with a '.', it is fully-qualified. Otherwise, C++-like scoping
// rules are used to find the type (i.e. first the nested types within this
// message are searched, then within the parent, on up to the root
// namespace).
optional string type_name = 6;ui
// For extensions, this is the name of the type being extended. It is
// resolved in the same manner as type_name.
optional string extendee = 2;this
// For numeric types, contains the original text representation of the value.
// For booleans, "true" or "false".
// For strings, contains the default text contents (not escaped in any way).
// For bytes, contains the C escaped value. All bytes >= 128 are escaped.
// TODO(kenton): Base-64 encode?
optional string default_value = 7;google
// If set, gives the index of a oneof in the containing type's oneof_decl
// list. This field is a member of that oneof.
optional int32 oneof_index = 9;spa
// JSON name of this field. The value is set by protocol compiler. If the
// user has set a "json_name" option on this field, that option's value
// will be used. Otherwise, it's deduced from the field's name by converting
// it to camelCase.
optional string json_name = 10;
optional FieldOptions options = 8;
}
protobuf-master\src\google\protobuf\descriptor.cc
void BuildField(const FieldDescriptorProto& proto,
const Descriptor* parent,
FieldDescriptor* result) {
BuildFieldOrExtension(proto, parent, result, false);
}
實際上是一個內存結構相對於數組基地址的偏移量,也就是一個數組index
protobuf-master\src\google\protobuf\descriptor.h
// To save space, index() is computed by looking at the descriptor's position
// in the parent's array of children.
inline int FieldDescriptor::index() const {
if (!is_extension_) {
return static_cast<int>(this - containing_type()->fields_);
} else if (extension_scope_ != NULL) {
return static_cast<int>(this - extension_scope_->extensions_);
} else {
return static_cast<int>(this - file_->extensions_);
}
}
因爲GetDescriptor和GetReflection都是首先調用GetDescriptor接口,因此一般在派生類接口中按需註冊便可:
// Get a non-owning pointer to a Descriptor for this message's type. This
// describes what fields the message contains, the types of those fields, etc.
// This object remains property of the Message.
const Descriptor* GetDescriptor() const { return GetMetadata().descriptor; }
// Get a non-owning pointer to the Reflection interface for this Message,
// which can be used to read and modify the fields of the Message dynamically
// (in other words, without knowing the message type at compile time). This
// object remains property of the Message.
//
// This method remains virtual in case a subclass does not implement
// reflection and wants to override the default behavior.
virtual const Reflection* GetReflection() const final {
return GetMetadata().reflection;
}
例以下面是google項目自帶的電話簿派生類實現的接口
::PROTOBUF_NAMESPACE_ID::Metadata Person::GetMetadata() const {
::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&::assign_descriptors_table_demo_2eproto);
return ::file_level_metadata_demo_2eproto[kIndexInFileMessages];
從這個例子看,這種反射並無太大實際用處,覺得類型是須要在編譯時肯定的,也就是不一樣的數據類型須要使用不一樣的接口來操做
tsecer@protobuf: cat addressbook.proto
syntax = "proto2";
package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
tsecer@protobuf: cat relection.cc
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
// file.
int main(int argc, char* argv[]) {
tutorial::Person person; person.set_id(1111); const google::protobuf::Reflection *pstRefl = person.GetReflection(); const google::protobuf::Descriptor *pstDesc = person.GetDescriptor(); const google::protobuf::FieldDescriptor *pstField = pstDesc->FindFieldByName("id"); printf("id before is %d\n", person.id()); pstRefl->SetInt32(&person, pstField, 2222); printf("id after is %d\n", person.id()); return 0;}tsecer@protobuf: protoc --cpp_out=. addressbook.proto tsecer@protobuf: g++ relection.cc addressbook.pb.cc -lprotobuf --std=c++11 ./tsecer@protobuf: ./a.out id before is 1111id after is 2222