|
1 说在前面
因为看到网上都是一些零零散散的 protobuf 相关介绍,加上笔者比来因为项目的原因深入分解了一下 protobuf,所以想做一个系统的《精通 protobuf 道理》系列的分享:
- 「精通 protobuf 道理之一:为什么要使用它以及如何使用」;
- 「精通 protobuf 道理之二:编码道理分解」;
- 「精通 protobuf 道理之三:一文彻底搞懂反射道理」;
- 「精通 protobuf 道理之四:反射实践,和json的彼此转换」;
- 「精通 protobuf 道理之五:一文彻底搞懂 RPC 道理」;
- 「精通 protobuf 道理之六:本身动手写一个 RPC 框架」;
- 「精通 protobuf 道理之七:一文彻底搞懂 Arena 分配器道理分解」。
- 后续的待定……
本文是系列文章的第三篇,主讲 protobuf 反射道理。本文适合 protobuf 入门、进阶的开发者阅读,是一篇讲道理的文章,主要是深入介绍了 protobuf 反射的底层道理。通过阅读本文,开发者能够对 protobuf 反射道理有深入的理解,对如何更好的运用 protobuf 反射特性提供很大的参考价值。
如果你还在为protobuf 反射道理存在很多问号?
如果让你本身实现一个反射组件,你还不知道怎么实现?
那么通过这篇文章,可以帮你解决这些问题。
文章内容有点长,可能需要阅读 5~10分钟。
2 什么是反射
这里所说的“反射”,指是法式在运行时能够动态的获取到一个类型的元信息的一种操作。而知道了该类型的元信息,就可以操作元信息构造出该类型的实例,并对该实例进行读写操作。和明确地指定一个变量的类型的区别是,后者是在编译阶段就已经生成了该类型的实例,而前者(反射)的过程是在运行时完成,或者说是在运行时推算出该实例的类型。
3 先上一个示例
还是以系列文章第一篇中的 echo.proto 为例。因为笔者感觉从示例入手,分析每一个法式都用到了什么东西,都做了哪些事情,这样能够让读者更容易代入,更容易理解。如果读者还有什么更好的方式,欢迎交流。
先上 echo.proto 源码:
syntax = ”proto3”;
package self;
option cc_generic_services = true;
enum QueryType {
PRIMMARY = 0;
SECONDARY = 1;
};
message EchoRequest {
QueryType querytype = 1;
string payload = 2;
}
message EchoResponse {
int32 code = 1;
string msg = 2;
}
service EchoService {
rpc Echo(EchoRequest) returns(EchoResponse);
}
测试源代码这么写(http://test_reflection.cc):
#include <iostream>
#include ”proto/echo.pb.h”
void test_relection() {
const std::string type_name = ”self.EchoRequest”;
/*
* ① 在 DescriptorPool 中检索 self.EchoRequest
* Message 类型的 discriptor 元数据
*/
const google::protobuf::Descriptor* descriptor
= google::protobuf::DescriptorPool::generated_pool()
->FindMessageTypeByName(type_name);
if (descriptor == nullptr) {
std::cout << ”[ERROR] Cannot found ” << type_name
<< ” in DescriptorPool” << std::endl;
return;
}
/*
* ② 通过 discriptor 元信息在 MessageFactory 检索类型工厂,
* 用于创建该类型的实例。
*/
const google::protobuf::Message* prototype
= google::protobuf::MessageFactory::generated_factory()
->GetPrototype(descriptor);
/*
* ③ 创建 self.EchoRequest 类型的 Message 实例。
* google::protobuf::Message 是所有 Message
* 类型的基类。
*/
google::protobuf::Message* req_msg = prototype->New();
/*
* ④ 因为只知道基类的实例指针,需要 Reflection 信息协助判断
* 具体类型。
*/
const google::protobuf::Reflection* req_msg_ref
= req_msg->GetReflection();
/*
* ⑤ 作为开发者,是知道该 Message 是对应哪个类型的,但是法式不知道,
* 开发者告诉法式,试着获取其 payload 字段。
*/
const google::protobuf::FieldDescriptor *req_msg_ref_field_payload
= descriptor->FindFieldByName(”payload”);
/*
* ⑥ Field 信息 + Reflection 信息配合读取 payload 的数据。
*/
std::cout << ”before set, ref_req_msg_payload: ”
<< req_msg_ref->GetString(*req_msg, req_msg_ref_field_payload)
<< std::endl;
/*
* ⑦ Field 信息 + Reflection 信息配合写入 payload 的数据。
*/
req_msg_ref->SetString(req_msg, req_msg_ref_field_payload, ”my payload”);
/*
* ⑧ Field 信息 + Reflection 信息配合再次读取 payload 的数据。
*/
std::cout << ”after set, ref_req_msg_payload: ”
<< req_msg_ref->GetString(*req_msg, req_msg_ref_field_payload)
<< std::endl;
}
int main() {
test_relection();
return 0;
}
看似写了很多源代码,但是其实就做了一个事情,定义个 self::EchoRequest 变量,然后对其进行读和写。编译执行得到成果:
$ ./test_reflection
before set, ref_req_msg_payload:
after set, ref_req_msg_payload: my payloadPS:需要注意的是这里其实有一个坑,笔者猜测可能是编译器优化的原因造成的。现象是会输出 “[ERROR] Cannot found in DescriptorPool”。因为main函数中没有使用到 echo.proto 中的任何类型,编译器认为没有使用到echo.proto 的代码,所以不让法式执行以下变量的初始化,从而导致索引没有初始化:
PROTOBUF_ATTRIBUTE_INIT_PRIORITY static ::PROTOBUF_NAMESPACE_ID::internal::AddDescriptorsRunner dynamic_init_dummy_echo_2eproto(&descriptor_table_echo_2eproto);
其道理会在后续的章节中涉及到。
解决法子:在 main 函数中加上一行 self::EchoRequest req 的代码即可,让编译器以为使用到了echo.proto 中的类型。 那么接下来笔者的思路就是通过分析 ① ~ ⑧ 各个法式的实现道理来了解反射是如何工作的。
4 道理分析
4.1 DescriptorPool 索引
4.1.1 google::protobuf::Descriptor
前面示例中有使用到 DescriptorPool 的 FindMessageTypeByName 接口函数(如下代码),这里的目的是获取该Message 的元信息。这里获取元信息的过程是一个查表的过程,本节主要了解一下此表的索引是什么道理,以及如何构建的。
const google::protobuf::Descriptor* descriptor
= google::protobuf::DescriptorPool::generated_pool()
->FindMessageTypeByName(type_name);
4.1.2 DescriptorPool 索引的构建时机
这里会用到 google::protobuf::internal::AddDescriptorsRunner(下简称 AddDescriptorsRunner),它的实现斗劲简单,如下代码,先看看它的原型。
struct PROTOBUF_EXPORT AddDescriptorsRunner {
explicit AddDescriptorsRunner(const DescriptorTable* table);
};
AddDescriptorsRunner::AddDescriptorsRunner(const DescriptorTable* table) {
AddDescriptors(table);
}
从如上代码中,可以看出执行构造函数的时候会触发 Descriptor 表的构建。那什么情况下会执行构造函数呢?我们在 http://echo.pb.cc 源代码文件中找到这样一行代码(如下),这行代码的感化是定义一个静态类型的 AddDescriptorsRunner 类型的变量,因为是静态类型的,所以在法式启动是生成,在法式退出时销毁。而在定义该变量时回触发构造函数的调用,所以我们不难理解,DescriptorPool 索引的构建时机是法式启动的时候,销毁时机是在法式退出的时候。
PROTOBUF_ATTRIBUTE_INIT_PRIORITY static ::PROTOBUF_NAMESPACE_ID::internal::AddDescriptorsRunner dynamic_init_dummy_echo_2eproto(&descriptor_table_echo_2eproto);
但是看到后面的章节你会发现,这里法式启动生成的索引只是很小一部门,在使用的时候(也就是查询的时候)还会触发构建一个完成的索引数据。具体原因也在该处说明。
4.1.3 DescriptorPool索引的构建道理
我们先从 AddDescriptors 函数开始分析。
void AddDescriptors(const DescriptorTable* table) {
if (table->is_initialized) return;
table->is_initialized = true;
AddDescriptorsImpl(table);
}
void AddDescriptorsImpl(const DescriptorTable* table) {
// Reflection refers to the default fields so make sure they are initialized.
internal::InitProtobufDefaults();
// Ensure all dependent descriptors are registered to the generated descriptor
// pool and message factory.
int num_deps = table->num_deps;
for (int i = 0; i < num_deps; i++) {
// In case of weak fields deps could be null.
if (table->deps) AddDescriptors(table->deps);
}
// Register the descriptor of this file.
DescriptorPool::InternalAddGeneratedFile(table->descriptor, table->size);
MessageFactory::InternalRegisterGeneratedFile(table);
}
可以看出,总共三个法式:
- 初始化变量:反射需要使用的变量,先确保其已经初始化 了;
- 解析依赖:如果有import 其他proto 源文件,那么先解析其他proto源文件,存在 deps 中。
- 注册 Descriptor:
- 构建 DescriptorPool 索引(DescriptorPool::InternalAddGeneratedFile);
- 构建MessageFactory 索引(MessageFactory::InternalRegisterGeneratedFile)。
本节我们主要分析构建 DescriptorPool 索引的实现道理,至于MessageFactory 索引的实现道理我们不才一节中再详细分析。
database 是一个斗劲抽象的名称,database 底层实现其实就是索引 index_,这里会先把文件定义信息先解析,主要是确定 encoded_file_descriptor 信息是正确的。
void DescriptorPool::InternalAddGeneratedFile(
const void* encoded_file_descriptor, int size) {
GOOGLE_CHECK(GeneratedDatabase()->Add(encoded_file_descriptor, size));
}
bool EncodedDescriptorDatabase::Add(const void* encoded_file_descriptor, int size) {
FileDescriptorProto file;
if (file.ParseFromArray(encoded_file_descriptor, size)) {
return index_->AddFile(file, std::make_pair(encoded_file_descriptor, size));
} else {
GOOGLE_LOG(ERROR) << ”Invalid file descriptor data passed to ”
”EncodedDescriptorDatabase::Add().”;
return false;
}
}
index_ 的类型是 DescriptorIndex的指针。
std::unique_ptr<DescriptorIndex> index_;
DescriptorIndex 的索引如下:
/*数据表,存的是最终数据,包罗:
/* - 文件元数据
* - 标签元数据
* - 扩展元数据 */
std::vector<EncodedEntry> all_values_;
/* 文件元数据索引,指向 all_values_ 的位置(即下标) */
std::set<FileEntry, FileCompare> by_name{FileCompare{*this}};
std::vector<FileEntry> by_name_flat;
/* 标签元数据索引,包罗:
* - message
* - enum
* - externion
* - service */
std::set<SymbolEntry, SymbolCompare> by_symbol{SymbolCompare{*this}};
std::vector<SymbolEntry> by_symbol_flat;
/* 扩展元数据索引 */
std::set<ExtensionEntry, ExtensionCompare> by_extension{ExtensionCompare{*this}};
std::vector<ExtensionEntry> by_extension_flat;
关系图如下图所示:
AddFile 实现如下:
template <typename FileProto>
bool EncodedDescriptorDatabase::DescriptorIndex::AddFile(const FileProto& file,
Value value) {
// We push `value` into the array first. This is important because the AddXXX
// functions below will expect it to be there.
//############## 数据表 ##############
all_values_.push_back({value.first, value.second, {}});
if (!ValidateSymbolName(file.package())) {
GOOGLE_LOG(ERROR) << ”Invalid package name: ” << file.package();
return false;
}
all_values_.back().encoded_package = EncodeString(file.package());
//############## 索引表 ##############
// 1. 文件元信息索引
if (!InsertIfNotPresent(
&by_name_, FileEntry{static_cast<int>(all_values_.size() - 1),
EncodeString(file.name())}) ||
std::binary_search(by_name_flat_.begin(), by_name_flat_.end(),
file.name(), by_name_.key_comp())) {
GOOGLE_LOG(ERROR) << ”File already exists in database: ” << file.name();
return false;
}
// 2. 类型索引
// - 所有类型城市进 symbol 表
// - externsion 会进入 externsion 表
for (const auto& message_type : file.message_type()) {
if (!AddSymbol(message_type.name())) return false;
if (!AddNestedExtensions(file.name(), message_type)) return false;
}
for (const auto& enum_type : file.enum_type()) {
if (!AddSymbol(enum_type.name())) return false;
}
for (const auto& extension : file.extension()) {
if (!AddSymbol(extension.name())) return false;
if (!AddExtension(file.name(), extension)) return false;
}
for (const auto& service : file.service()) {
if (!AddSymbol(service.name())) return false;
}
return true;
}
讲到这里,构建索引的实现道理就告了一段落,但是你以为构建索引已经结束了吗?当然没有!不才一节中分析。
4.1.4 DescriptorPool索引的查询过程
我们还是从一行代码开始(DescriptorPool 的 FindMessageTypeByName 接口函数)。
const google::protobuf::Descriptor* descriptor
= google::protobuf::DescriptorPool::generated_pool()
->FindMessageTypeByName(”self.EchoRequest”);
FindMessageTypeByName 函数实际上调用了 tables_ 成员的 FindByNameHelper 成员函数。
const Descriptor* DescriptorPool::FindMessageTypeByName(
ConstStringParam name) const {
Symbol result = tables_->FindByNameHelper(this, name);
return (result.type == Symbol::MESSAGE) ? result.descriptor : nullptr;
}
FindByNameHelper 函数也并不复杂,首先查表,如果miss,就会调用 TryFindSymbolInFallbackDatabase 进行索引构建(这里会用到之前讲过的 DescriptorIndex 的信息)。源代码如下:
这里刻意略过 underlay,underlay 这个特性笔者猜测是为了效率实现的多层cache,underlay 也就是下层的意思,逻辑都是一样的,这里我们没有涉及 underlay,就先不展开分析。 Symbol DescriptorPool::Tables::FindByNameHelper(const DescriptorPool* pool,
StringPiece name) {
if (pool->mutex_ != nullptr) {
// Fast path: the Symbol is already cached. This is just a hash lookup.
ReaderMutexLock lock(pool->mutex_);
if (known_bad_symbols_.empty() && known_bad_files_.empty()) {
Symbol result = FindSymbol(name);
if (!result.IsNull()) return result;
}
}
MutexLockMaybe lock(pool->mutex_);
if (pool->fallback_database_ != nullptr) {
known_bad_symbols_.clear();
known_bad_files_.clear();
}
Symbol result = FindSymbol(name);
if (result.IsNull() && pool->underlay_ != nullptr) {
// Symbol not found; check the underlay.
result = pool->underlay_->tables_->FindByNameHelper(pool->underlay_, name);
}
if (result.IsNull()) {
// Symbol still not found, so check fallback database.
if (pool->TryFindSymbolInFallbackDatabase(name)) {
result = FindSymbol(name);
}
}
return result;
}
分析 FindSymbol ,发现其查的是 symbols_by_name_ 这个索引表(其定义如下),但是这个表我们还没有构建啊。是的,之前没有构建过,但是为什么需要等待这个时候才构建呢?笔者认为有两个原因:一个是内存占用原因,如果没有改proto文件没有被使用到,就不需要前置构建,占用内存;另一个是启动效率原因,没有必要为了没有被使用到的proto文件做无用功,而且就算后续使用到了再构建,也只是第一个使用者会牺牲一些效率(如果读者有认为是其他什么原因导致这样设计,欢迎来交流和探讨)。
typedef HASH_MAP<StringPiece, Symbol, HASH_FXN<StringPiece>> SymbolsByNameMap;
class DescriptorPool::Tables {
...
SymbolsByNameMap symbols_by_name_;
}
最终会使用 DescriptorBuilder来进行 symbol 索引的构建:TryFindSymbolInFallbackDatabase -> BuildFileFromDatabase -> DescriptorBuilder().BuildFile(proto) ,BuildFile -> BuildFileImpl,BuildFileImpl 如下:
FileDescriptor* BuildFileImpl(const FileDescriptorProto& proto) {
...
BUILD_ARRAY(proto, result, message_type, BuildMessage, nullptr);
BUILD_ARRAY(proto, result, enum_type, BuildEnum, nullptr);
BUILD_ARRAY(proto, result, service, BuildService, nullptr);
BUILD_ARRAY(proto, result, extension, BuildExtension, nullptr);
...
}
BuildFileImpl 会针对每个 message_type、enum_type、service、extension 构建索引,举一个 BuildMessage 的例子。
void BuildMessage(const DescriptorProto& proto,
const Descriptor* parent,
Descriptor* result) {
...
BUILD_ARRAY(proto, result, oneof_decl, BuildOneof, result);
BUILD_ARRAY(proto, result, field, BuildField, result);
BUILD_ARRAY(proto, result, nested_type, BuildMessage, result);
BUILD_ARRAY(proto, result, enum_type, BuildEnum, result);
BUILD_ARRAY(proto, result, extension_range, BuildExtensionRange, result);
BUILD_ARRAY(proto, result, extension, BuildExtension, result);
BUILD_ARRAY(proto, result, reserved_range, BuildReservedRange, result);
...
AddSymbol(...);
)
再看 AddSymbol 函数,从其代码可以看出会写两个表:一个是 symbols_by_name_ ,另一个是 symbols_by_parernt_ ,前者是通过定名来查找,后者是通过父类型调用触发的查找。
bool DescriptorBuilder::AddSymbol(const std::string& full_name,
const void* parent, const std::string& name,
const Message& proto, Symbol symbol) {
...
if (tables_->AddSymbol(full_name, symbol)) {
if (!file_tables_->AddAliasUnderParent(parent, name, symbol)) {
..
}
这两个表分布在分歧的处所,symbols_by_name_ 是 DescriptorPool::Tables 类中,一般是全局搜索某个类型需要调用到,而symbols_by_parent_ 是在 FileDescriptorTables 类中,一般我们用来查询当前类型的某个字段(即field)用到斗劲多。
typedef HASH_MAP<StringPiece, Symbol, HASH_FXN<StringPiece>> SymbolsByNameMap;
class DescriptorPool::Tables {
...
SymbolsByNameMap symbols_by_name_;
}
class FileDescriptorTables {
...
SymbolsByParentMap symbols_by_parent_;
}
Symbol 是个抽象的类型,可以暗示proto文件中的所有类型。所以,symbols_by_name_ 和 symbols_by_parent_ 这两个表也用来存储所有的类型。
4.2 MessageFactory 索引
4.2.1 google::protobuf::Message
这是所有Message 类型的基类,所以用他来暗示索引的类型。
4.2.2 MessageFactory 索引的构建时机
和DescriptorPool索引的构建时机不异,法式启动的时候构建了一部门索引,而在使用(也就是查询的时候)还会触发构建完整的Message索引数据。
4.2.3 MessageFactory 索引的构建道理
还是从AddDescriptorsImpl函数接口开始。这个函数是在法式启动的时候触发执行的。
void AddDescriptorsImpl(const DescriptorTable* table) {
...
MessageFactory::InternalRegisterGeneratedFile(table);
}
void MessageFactory::InternalRegisterGeneratedFile(
const google::protobuf::internal::DescriptorTable* table) {
GeneratedMessageFactory::singleton()->RegisterFile(table);
}
GeneratedMessageFactory 类的定义如下,我们主要存眷两个成员 file_map_ 和 type_map_ ,但实际上最有用的是type_map_ , file_map_ 只是辅助感化。那为什么这里只是构建了 type_map_ 呢?笔者认为和 DescriptorPool 索引的原因是一样的,一个是内存占用原因,另一个是启动效率原因(如果有认为是其他什么原因导致这样设计,欢迎探讨)。
class GeneratedMessageFactory final : public MessageFactory {
public:
//构建 file_map_
void RegisterFile(const google::protobuf::internal::DescriptorTable* table);
//构建 type_map_
void RegisterType(const Descriptor* descriptor, const Message* prototype);
const Message* GetPrototype(const Descriptor* type) override;
private:
// Only written at static init time, so does not require locking.
HASH_MAP<StringPiece, const google::protobuf::internal::DescriptorTable*,
STR_HASH_FXN> file_map_;
...
std::unordered_map<const Descriptor*, const Message*> type_map_;
};
4.2.4 MessageFactory 索引的查询过程
从开发者怎么使用说起吧。开发者一般是调用 GetPrototype 函数来获取Messgae 实例。
const google::protobuf::Message* prototype
= google::protobuf::MessageFactory::generated_factory()
->GetPrototype(descriptor);
GetPrototype 函数的逻辑也很简单,先查 type_map_ 表,如果查不到,再按照 file_map_ 中的信息构建 type_map_ 表索引(见 internal::RegisterFileLevelMetadata 函数)。
const Message* GeneratedMessageFactory::GetPrototype(const Descriptor* type) {
{
/* 如果是第一次查询,那这里的查询成果是 Miss */
ReaderMutexLock lock(&mutex_);
const Message* result = FindPtrOrNull(type_map_, type);
if (result != NULL) return result;
}
...
// Apparently the file hasn't been registered yet. Let's do that now.
const internal::DescriptorTable* registration_data =
FindPtrOrNull(file_map_, type->file()->name().c_str());
...
WriterMutexLock lock(&mutex_);
/* 如果查询成果为 Miss
* 那么 需要调用 internal::RegisterFileLevelMetadata 构建 type_map_ 索引 */
// Check if another thread preempted us.
const Message* result = FindPtrOrNull(type_map_, type);
if (result == NULL) {
// Nope. OK, register everything.
internal::RegisterFileLevelMetadata(registration_data);
// Should be here now.
result = FindPtrOrNull(type_map_, type);
}
...
return result;
}
RegisterFileLevelMetadata 函数的一系列实现如下:
void RegisterFileLevelMetadata(const DescriptorTable* table) {
AssignDescriptors(table);
RegisterAllTypesInternal(table->file_level_metadata, table->num_messages);
}
void RegisterAllTypesInternal(const Metadata* file_level_metadata, int size) {
for (int i = 0; i < size; i++) {
const Reflection* reflection = file_level_metadata.reflection;
MessageFactory::InternalRegisterGeneratedMessage(
file_level_metadata.descriptor,
reflection->schema_.default_instance_);
}
}
void MessageFactory::InternalRegisterGeneratedMessage(
const Descriptor* descriptor, const Message* prototype) {
GeneratedMessageFactory::singleton()->RegisterType(descriptor, prototype);
}
void GeneratedMessageFactory::RegisterType(const Descriptor* descriptor,
const Message* prototype) {
...
if (!InsertIfNotPresent(&type_map_, descriptor, prototype)) {
GOOGLE_LOG(DFATAL) << ”Type is already registered: ” << descriptor->full_name();
}
}
最终是把 prototype(也就是 reflection->schema.default_instance )插入 type_map_ 表中,我们回到 http://echo.pb.cc 源代码文件,见以下源代码:
struct EchoRequestDefaultTypeInternal {
constexpr EchoRequestDefaultTypeInternal()
: _instance(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized{}) {}
~EchoRequestDefaultTypeInternal() {}
union {
EchoRequest _instance;
};
};
PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT EchoRequestDefaultTypeInternal _EchoRequest_default_instance_;
default_instance_ 指向的就是 instance。因为Message 都实现了 New 函数,可以通过 default_instance->New()创建出 Message 实例,即使不知道其真实类型是 EchoRequest。
inline EchoRequest* New() const final {
return new EchoRequest();
}
4.3 实例创建接口
通过 New 函数接口实现,实际上调用的 EchoRequest 的 New 函数,返回值为 EchoRequest *,而 EchoRequest 担任了 google::protobuf::Message 类。
google::protobuf::Message* req_msg = prototype->New();
4.4 Reflection 成员
还是以 EchoRequest 为例子。
message EchoRequest {
QueryType querytype = 1;
string payload = 2;
}
如果需要对 payload 字段读写,那我们直接使用 set_payload 和 get_payload 这两个函数接口就可以了。但是如果是使用 google::protobuf::Message 基类指针类型来操作,它是没有 set_payload 和 get_payload 这两个接口函数的。这个时候 Reflection (即 google::protobuf::Reflection)呈现了,它类似一个代办代理人的角色,可以辅佐做一些读写的操作。如下 SetString、GetString 函数。Reflection 类过于复杂,这里就不详细分析,感兴趣的读者可以自行阅读源代码。
class PROTOBUF_EXPORT Reflection final {
public:
...
void SetString(Message* message, const FieldDescriptor* field,
std::string value) const;
std::string GetString(const Message& message,
const FieldDescriptor* field) const;
...
};
4.5 字段索引(Field)
前面「4.1.4 DescriptorPool索引的查询过程」章节中介绍了构建symbol 索引的过程,字段(即field)索引也是在阿谁时候解析并构建的。如下使用到了 BuildField 函数进行字段索引构建。
void BuildField(const FieldDescriptorProto& proto, Descriptor* parent,
FieldDescriptor* result) {
BuildFieldOrExtension(proto, parent, result, false);
}
void BuildFieldOrExtension(const FieldDescriptorProto& proto,
Descriptor* parent, FieldDescriptor* result,
bool is_extension);
...
AddSymbol(result->full_name(), parent, result->name(), proto, Symbol(result));
}
protobuf 中使用FieldDescriptor来描述字段(field),如下所示:
class PROTOBUF_EXPORT FieldDescriptor {
public:
typedef FieldDescriptorProto Proto;
// Identifies a field type. 0 is reserved for errors. The order is weird
// for historical reasons. Types 12 and up are new in proto2.
enum Type {
TYPE_DOUBLE = 1, // double, exactly eight bytes on the wire.
TYPE_FLOAT = 2, // float, exactly four bytes on the wire.
TYPE_INT64 = 3, // int64, varint on the wire. Negative numbers
// take 10 bytes. Use TYPE_SINT64 if negative
// values are likely.
TYPE_UINT64 = 4, // uint64, varint on the wire.
TYPE_INT32 = 5, // int32, varint on the wire. Negative numbers
// take 10 bytes. Use TYPE_SINT32 if negative
// values are likely.
TYPE_FIXED64 = 6, // uint64, exactly eight bytes on the wire.
TYPE_FIXED32 = 7, // uint32, exactly four bytes on the wire.
TYPE_BOOL = 8, // bool, varint on the wire.
TYPE_STRING = 9, // UTF-8 text.
TYPE_GROUP = 10, // Tag-delimited message. Deprecated.
TYPE_MESSAGE = 11, // Length-delimited message.
TYPE_BYTES = 12, // Arbitrary byte array.
TYPE_UINT32 = 13, // uint32, varint on the wire
TYPE_ENUM = 14, // Enum, varint on the wire
TYPE_SFIXED32 = 15, // int32, exactly four bytes on the wire
TYPE_SFIXED64 = 16, // int64, exactly eight bytes on the wire
TYPE_SINT32 = 17, // int32, ZigZag-encoded varint on the wire
TYPE_SINT64 = 18, // int64, ZigZag-encoded varint on the wire
MAX_TYPE = 18, // Constant useful for defining lookup tables
// indexed by Type.
};
// Specifies the C++ data type used to represent the field. There is a
// fixed mapping from Type to CppType where each Type maps to exactly one
// CppType. 0 is reserved for errors.
enum CppType {
CPPTYPE_INT32 = 1, // TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32
CPPTYPE_INT64 = 2, // TYPE_INT64, TYPE_SINT64, TYPE_SFIXED64
CPPTYPE_UINT32 = 3, // TYPE_UINT32, TYPE_FIXED32
CPPTYPE_UINT64 = 4, // TYPE_UINT64, TYPE_FIXED64
CPPTYPE_DOUBLE = 5, // TYPE_DOUBLE
CPPTYPE_FLOAT = 6, // TYPE_FLOAT
CPPTYPE_BOOL = 7, // TYPE_BOOL
CPPTYPE_ENUM = 8, // TYPE_ENUM
CPPTYPE_STRING = 9, // TYPE_STRING, TYPE_BYTES
CPPTYPE_MESSAGE = 10, // TYPE_MESSAGE, TYPE_GROUP
MAX_CPPTYPE = 10, // Constant useful for defining lookup tables
// indexed by CppType.
};
// Identifies whether the field is optional, required, or repeated. 0 is
// reserved for errors.
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.
};
...
//因为一个field 只有一个类型,
//所以使用内联布局,节省内存,
union {
int32 default_value_int32_;
int64 default_value_int64_;
uint32 default_value_uint32_;
uint64 default_value_uint64_;
float default_value_float_;
double default_value_double_;
bool default_value_bool_;
mutable const EnumValueDescriptor* default_value_enum_;
const std::string* default_value_string_;
mutable std::atomic<const Message*> default_generated_instance_;
};
...
};
4.6 操作反射访谒字段的API
结合 Reflection 来分析一下 field 的使用。看 field->default_value_string() 这一行,其实是返回了上述 union 中的 default_value_string_ 成员。
4.6.1 Reflection::GetString
std::string Reflection::GetString(const Message& message,
const FieldDescriptor* field) const {
USAGE_CHECK_ALL(GetString, SINGULAR, STRING);
if (field->is_extension()) {
return GetExtensionSet(message).GetString(field->number(),
field->default_value_string());
} else {
if (schema_.InRealOneof(field) && !HasOneofField(message, field)) {
return field->default_value_string();
}
switch (field->options().ctype()) {
default: // TODO(kenton): Support other string reps.
case FieldOptions::STRING: {
if (auto* value =
GetField<ArenaStringPtr>(message, field).GetPointer()) {
return *value;
}
return field->default_value_string();
}
}
}
}
4.6.2 Reflection::SetString
void Reflection::SetString(Message* message, const FieldDescriptor* field,
std::string value) const {
USAGE_CHECK_ALL(SetString, SINGULAR, STRING);
if (field->is_extension()) {
return MutableExtensionSet(message)->SetString(
field->number(), field->type(), std::move(value), field);
} else {
switch (field->options().ctype()) {
default: // TODO(kenton): Support other string reps.
case FieldOptions::STRING: {
// Oneof string fields are never set as a default instance.
// We just need to pass some arbitrary default string to make it work.
// This allows us to not have the real default accessible from
// reflection.
const std::string* default_ptr =
schema_.InRealOneof(field)
? nullptr
: DefaultRaw<ArenaStringPtr>(field).GetPointer();
if (schema_.InRealOneof(field) && !HasOneofField(*message, field)) {
ClearOneof(message, field->containing_oneof());
MutableField<ArenaStringPtr>(message, field)
->UnsafeSetDefault(default_ptr);
}
MutableField<ArenaStringPtr>(message, field)
->Set(default_ptr, std::move(value),
message->GetArenaForAllocation());
break;
}
}
}
}
5 小结一下
- 法式启动时会先初始化一部门索引,这是轻量级的,只是一些基础的数据,为下一步构建全量索引做筹备。
- DescriptorTool 的 EncodedEntry、FileEntry、SymbolEntry、ExtensionEntry;
- MessageFactory的 name->DescriptorTable。
- 当使用到某个类型的时候,会触发对该proto文件的全量索引的构建;
- DescriptorTool 的 name-> Symbol、parent -> Symbol;
- MessageFactory 的 Descriptor->Message。
- Reflection 作为 Message 的一个代办代理人,结合 Descriptor 和 Message的接口,对 Message 进行读写操作。
- 反射的一般使用场景:
- 和其他数据布局比如 json、xml 等的彼此转换;
- 保举系统中的特征抽取(平台化,所以需要对数据类型进行可配置化)。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|