持續更新中。。。
何爲反射機制
基本概念
指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力
程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。您可以使用反射動態地創建類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型。然後,可以調用類型的方法或訪問其字段和屬性。
我(c++程序員)關注的問題
- 如何在程序運行過程中通過類型名字(一個字符串,合法但是內容在編譯期間未知,比如是在配置文件中獲取的)創建出類型對象.
- 如果在程序運行過程中通過對象和對象的屬性的名字(一個字符串,合法但是內容在編譯期間未知,比如是通過通訊包獲取的)獲取,修改對應屬性.
- 如果在程序運行過程中通過對象和對象方法的名字(一個字符串,合法但是內容在編譯期間未知,比如是從用戶輸入獲取的)調用對應的方法.
protobuf 反射使用簡介
舉個例子 :
當你有一個test.proto 比如 :
package T;
message Test
{
optional int32 id = 1;
}
通過類型名字創建出類型對象.
- 預先編譯好proto模式
//! 利用類型名字構造對象.
/*!
* @Param type_name 類型名字,比如 "Test.TestMessage".
* @Return 對象指針,new 出來的,使用者負責釋放.
*/
#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>
#include "cpp/test.pb.h" // 這是protoc給你生成的文件
int main()
{
// 先獲得類型的Descriptor .
auto descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("T.Test");
if (nullptr == descriptor)
{
return 0 ;
}
// 利用Descriptor拿到類型註冊的instance. 這個是不可修改的.
auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
if ( nullptr == descriptor)
{
return 0 ;
}
// 構造一個可用的消息.
auto message = prototype->New();
// 只有當我們預先編譯了test消息並且正確鏈接才能這麼幹.
auto test = dynamic_cast<T::Test*>(message);
// 直接調用message的具體接口
// 其實這些接口是語法糖接口.所以並沒有對應的反射機制來對應調用.
// 反射機制實現了的Set/Get XXX系列接口,是屬於Reflection的接口,接收Message作爲參數.
test->set_id(1);
std::cout<<test->Utf8DebugString()<<std::endl;
delete message ;
return 0 ;
}
- 直接解析proto 文件模式
#include <iostream>
#include <google/protobuf/compiler/importer.h>
#include <google/protobuf/dynamic_message.h>
int main()
{
// 準備配置好文件系統
google::protobuf::compiler::DiskSourceTree sourceTree;
// 將當前路徑映射爲項目根目錄 , project_root 僅僅是個名字,你可以你想要的合法名字.
sourceTree.MapPath("project_root","./");
// 配置動態編譯器.
google::protobuf::compiler::Importer importer(&sourceTree, NULL);
// 動態編譯proto源文件。 源文件在./source/proto/test.proto .
importer.Import("project_root/source_proto/test.proto");
// 現在可以從編譯器中提取類型的描述信息.
auto descriptor1 = importer.pool()->FindMessageTypeByName("T.Test");
// 創建一個動態的消息工廠.
google::protobuf::DynamicMessageFactory factory;
// 從消息工廠中創建出一個類型原型.
auto proto1 = factory.GetPrototype(descriptor1);
// 構造一個可用的消息.
auto message1= proto1->New();
// 下面是通過反射接口給字段賦值.
auto reflection1 = message1->GetReflection();
auto filed1 = descriptor1->FindFieldByName("id");
reflection1->SetInt32(message1,filed1,1);
// 打印看看
std::cout << message1->DebugString();
// 刪除消息.
delete message1 ;
return 0 ;
}
通過對象和對象的屬性的名字獲取,修改對應屬性.
首先定義mesage :
對於上文提到的 test.proto
#include "cpp/test.pb.h"
#include <iostream>
int main()
{
// 拿到一個對象,不在乎怎麼拿到,可以是通過反射拿到。
// 這裏簡單直接的創建一個.
T::Test p_test ;
// 拿到對象的描述包.
auto descriptor = p_test.GetDescriptor() ;
// 拿到對象的反射配置.
auto reflecter = p_test.GetReflection() ;
// 拿到屬性的描述包.
auto field = descriptor->FindFieldByName("id");
// 設置屬性的值.
reflecter->SetInt32(&p_test , field , 5 ) ;
// 獲取屬性的值.
std::cout<<reflecter->GetInt32(p_test , field)<< std::endl ;
return 0 ;
}
通過對象和對象方法的名字調用對應的方法.
//TODO
protobuf 反射實現解析.
基本概念
Descriptor系列.
::google::protobuf::Descriptor
系列包括:
- Descriptor – 用來描述 消息
- FieldDescriptor – 用來描述 字段
- OneofDescriptor – 用來描述 聯合體
- EnumDescriptor – 用來描述 枚舉
- EnumValueDescriptor – 用來描述 枚舉值
- ServiceDescriptor – 用來描述 服務器
- MethodDescriptor – 用來描述 服務器方法
- FileDescriptor – 用來描述 文件
這些 Descriptor
系列的數據則是由 DescriptorProto
系列的數據 , 利用DescriptorBuilder工具類來填充的 .
有興趣的可以查閱 https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.cc
DescriptorProto
系列是一些用protobuf定義的,用來描述所有由protbuf產生的類型的類型信息包.
對應的proto文件在 : https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto
Descriptor
系列最大的message是 FileDescriptor
. 每個文件會生成一個包含本文件所有信息的FileDescriptor
包.
舉個例子 :
對於上文提到的 test.proto , protoc 會給就會自動填裝一個描述包,類似於:
::google::protobuf::FileDescriptorProto file;
file.set_name("test.proto");
file.set_packet("T")
auto desc = file.add_message_type() ;
desc->set_name("T.Test");
auto id_desc = desc->mutable_field();
id_desc->set_name("id");
id_desc->set_type(::google::protobuf::FieldDescriptorProto::TYPE_INT32);
id_desc->set_number(1);
//...
然後保存起來.
如果你讀protoc生成的 test.pb.cc文件 你會看到這樣的代碼 :
::google::protobuf::DescriptorPool::InternalAddGeneratedFile(
"\n\013test.proto\022\001T\"\022\n\004Test\022\n\n\002id\030\001 \001(\005", 36);
其實就是在protoc生成代碼中hard code 了對應proto文件的FileDescriptor
包序列化之後的數據. 作爲參數直接使用.
offset
任何一個對象最終都對應一段內存,有內存起始(start_addr
)和結束地址,
而對象的每一個屬性,都位於 start_addr+$offset
,所以當對象和對應屬性的offset已知的時候,
屬性的內存地址也就是可以獲取的。
//! 獲取某個屬性在對應類型對象的內存偏移.
#define GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(TYPE, FIELD) \
static_cast<int>( \
reinterpret_cast<const char*>( \
&reinterpret_cast<const TYPE*>(16)->FIELD) - \
reinterpret_cast<const char*>(16))
DescriptorDatabase
DescriptorDatabase
是一個純虛基類,描述了一系列符合通過名字(文件名,符號名。。。) 來獲取FileDescriptorProto
的接口 :
https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor_database.h
// 這裏我幹掉了裏面的英文註釋.
class LIBPROTOBUF_EXPORT DescriptorDatabase {
public:
inline DescriptorDatabase() {}
virtual ~DescriptorDatabase();
virtual ~DescriptorDatabase();
// 通過文件名字找.
virtual bool FindFileByName(const string& filename,
FileDescriptorProto* output) = 0;
// 通過符號名字找.
virtual bool FindFileContainingSymbol(const string& symbol_name,
FileDescriptorProto* output) = 0;
// 通過擴展信息找.
virtual bool FindFileContainingExtension(const string& containing_type,
int field_number,
FileDescriptorProto* output) = 0;
// 通過擴展信息的tag數字找...
virtual bool FindAllExtensionNumbers(const string& /* extendee_type */,
vector<int>* /* output */) {
return false;
}
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DescriptorDatabase);
};
核心的兩個派生類是 :
EncodedDescriptorDatabase
- 支持
DescriptorDatabase
的全部接口 - 接收序列化之後的
FileDescriptorProto
, 保存在map
中備查. - 這個類對應着預先編譯鏈接好的那些類型的反射機制。
- 支持
SourceTreeDescriptorDatabase
- 僅支持
DescriptorDatabase
的FindFileByName
接口。其餘直接返回false. - 每次查詢某個文件都是從磁盤讀入proto的源文件,編譯解析後返回對應的
FileDescriptorProto
. - 這個類對應着動態編譯proto源文件的時候的反射機制.
- 僅支持
這裏我不探究protobuf 是如何運行時編譯proto源文件.
DescriptorPool
任何時候想要查詢一個Descriptor
, 都是去DescriptorPool
裏面查詢。
DescriptorPool
實現了這樣的機制 :
- 緩存所有查詢的文件的
Descriptor
。 - 查找
Descriptor
的時候,如果自身緩存查到就直接返回結果,
否則去自帶的DescriptorDatabase
中查FileDescriptorProto
,
查到就轉化成Descriptor
, 返回結果並且緩存.
https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.h
class LIBPROTOBUF_EXPORT DescriptorPool{
public:
// Create a normal, empty DescriptorPool.
DescriptorPool();
// 幹掉一個灰常長的註釋,核心是下面兩條加一些注意事項.
// 構造一個帶着DescriptorDatabase的Pool 。
// 這樣查找的時候,優先從Pool中查找,找不到就到fallback_database中找.
class ErrorCollector;
explicit DescriptorPool(DescriptorDatabase* fallback_database,
ErrorCollector* error_collector = NULL);
~DescriptorPool();
// 這個獲取編譯進入二進制的那些消息的pool 。這個接口就是我們獲取預先編譯鏈接好
// 的消息的入口。
// Get a pointer to the generated pool. Generated protocol message classes
// which are compiled into the binary will allocate their descriptors in
// this pool. Do not add your own descriptors to this pool.
static const DescriptorPool* generated_pool();
// Find a FileDescriptor in the pool by file name. Returns NULL if not
// found.
const FileDescriptor* FindFileByName(const string& name) const;
// .... 一系列Find XXX By XXX 接口 ... , 不全部複製了.
// Building descriptors --------------------------------------------
class LIBPROTOBUF_EXPORT ErrorCollector {
// 不關心這個錯誤收集類...
};
// 這個是用FileDescriptorProto 填充FileDescriptor的接口.
const FileDescriptor* BuildFile(const FileDescriptorProto& proto);
// Same as BuildFile() except errors are sent to the given ErrorCollector.
const FileDescriptor* BuildFileCollectingErrors(
const FileDescriptorProto& proto,
ErrorCollector* error_collector);
// 依賴相關接口.
void AllowUnknownDependencies() { allow_unknown_ = true; }
void EnforceWeakDependencies(bool enforce) { enforce_weak_ = enforce; }
// Internal stuff --------------------------------------------------
// 一系列實現細節的接口。。 不復制。。。
private:
// 一系列實現細節的接口。。 不復制。。。
// 當從pool本身的table找不到的時候,試圖從database中查找的接口。
bool TryFindFileInFallbackDatabase(const string& name) const;
bool TryFindSymbolInFallbackDatabase(const string& name) const;
bool TryFindExtensionInFallbackDatabase(const Descriptor* containing_type,
int field_number) const;
// 一系列實現細節的接口。。 不復制。。。
// See constructor.
DescriptorDatabase* fallback_database_; // 持有的datebase
scoped_ptr<Tables> tables_; // Pool自身的table。 會緩存所有查過的文件內容.
// 一系列實現細節的接口。。 不復制。。。
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DescriptorPool);
}
核心的查找接口
https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.cc
Symbol DescriptorPool::Tables::FindByNameHelper(
const DescriptorPool* pool, const string& name) {
MutexLockMaybe lock(pool->mutex_);
known_bad_symbols_.clear();
known_bad_files_.clear();
//先從緩存中查詢.
Symbol result = FindSymbol(name);
// 這個是內部實現的細節 不要在意
if (result.IsNull() && pool->underlay_ != NULL) {
// 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;
}
MessageFactory
任何時候想要獲取一個類型的instance , 都要去MessageFactory
裏面獲取。
MessageFactory
是一個純虛的基類,定義了通過Descripor來獲取對應類型instance的接口.
{
public:
inline MessageFactory() {}
virtual ~MessageFactory();
// 了通過Descripor來獲取對應類型instance 的接口
virtual const Message* GetPrototype(const Descriptor* type) = 0;
// 這個是獲取編譯鏈接好的那些類型的factory單例的入口.
static MessageFactory* generated_factory();
// 這個是對應的像上面哪個單例內填裝數據的接口,protoc自動生成的文件都會有調用.
static void InternalRegisterGeneratedMessage(const Descriptor* descriptor,
const Message* prototype);
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MessageFactory);
}
同樣有兩個核心的派生類
GeneratedMessageFactory
- 一個map , 保存着
Descriptor
和Message *
- 這個類對應着預先編譯鏈接好的那些類型的反射機制。
- 一個map , 保存着
DynamicMessageFactory
- 有簡單的緩存,保存自己解析過的
Descriptor`` </li>
Descriptor“,動態的基於內存構造出一個
<li>可以通過Message
!!!
- 有簡單的緩存,保存自己解析過的
解決問題的辦法
通過類型名字創建出類型對象 — 預編譯proto並且鏈接進入二進制.
查表!!
是的,你沒猜錯,就是查表!!!
數據存儲在哪裏
所有的Descriptor
存儲在單例的DescriptorPool
中。google::protobuf::DescriptorPool::generated_pool()
來獲取他的指針。
所有的instance
存儲在單例的MessageFactory
中。google::protobuf::MessageFactory::generated_factory()
來獲取他的指針。將所有的Descriptor & instance 提前維護到表中備查
在protoc 生成的每個cc文件中, 都會有下面的代碼(protobuf V2 版本) :
// xxx 應該替換爲文件名,比如test.proto的test.
namespace {
//! 將本文件內的全部類型的instance註冊進入MessageFactory的接口.
void protobuf_RegisterTypes(const ::std::string&) {
// 初始化本文件的reflection數據.
protobuf_AssignDescriptorsOnce();
::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
Test_descriptor_, &Test::default_instance());
}
//! 本文件的初始接口.
void protobuf_AddDesc_xxx_2eproto() {
static bool already_here = false;
if (already_here) return;
already_here = true;
GOOGLE_PROTOBUF_VERIFY_VERSION;
// 註冊本文件的Descriptor包. 這樣就可以用名字通過generated_pool獲取對應的Descriptor。
::google::protobuf::DescriptorPool::InternalAddGeneratedFile(
"\n\013xxx.proto\022\001T\"\022\n\004Test\022\n\n\002id\030\001 \001(\005", 36);
// 將本文件的類型instance註冊接口註冊給MessageFactory.
// 這裏註冊接口是爲了實現類型的lazy註冊。如果沒有使用請求某個文件的類型,就不註冊對應文件的類型。
::google::protobuf::MessageFactory::InternalRegisterGeneratedFile(
"xxx.proto", &protobuf_RegisterTypes);
// 構造並且初始化全部instance.
Test::default_instance_ = new Test();
Test::default_instance_->InitAsDefaultInstance();
// 註冊清理接口.
::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_xxx_2eproto);
}
//! 下面利用全局變量的構造函數確保main函數執行之前數據已經進行註冊.
struct StaticDescriptorInitializer_xxx_2eproto {
StaticDescriptorInitializer_xxx_2eproto() {
protobuf_AddDesc_xxx_2eproto();
}
} static_descriptor_initializer_xxx_2eproto_;
}
通過類型名字創建出類型對象 — 運行時態編譯proto
這裏要引入 Importer
類.
class LIBPROTOBUF_EXPORT Importer {
public:
// 需要使用SourceTree來構造,
// 不過SourceTree最終是用來構造SourceTreeDescriptorDatabase的。
Importer(SourceTree* source_tree,
MultiFileErrorCollector* error_collector);
~Importer();
// 這個就是運行時態加載proto源文件的接口.
// 多次調用同一個文件只有第一次有效。
const FileDescriptor* Import(const string& filename);
// 拿到DescriptorPool 的接口.
// 每個Importer都有自己的DescriptorPool。
inline const DescriptorPool* pool() const {
return &pool_;
}
// 下面是咱不在意的接口.
void AddUnusedImportTrackFile(const string& file_name);
void ClearUnusedImportTrackFiles();
private:
// 這兩個數據成員很好的解釋了Importer如何工作 :
// 有 SourceTreeDescriptorDatabase 構造一個DescriptorPool,這樣
// 每當有文件被查找的時候,如果緩存中有,直接返回,如果沒有,
// SourceTreeDescriptorDatabase 自然會去加載解析源文件.
// Import接口則是提前將proto解析加載進入緩存的途徑.
SourceTreeDescriptorDatabase database_;
DescriptorPool pool_;
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(Importer);
}
通過對象和對象的屬性的名字獲取,修改對應屬性.
- GeneratedMessageReflection 的填裝和獲取
對於每一個message , 都有一個對應的GeneratedMessageReflection 對象.
這個對象保存了對應message反射操作需要的信息.
//!初始化本文件的所有GeneratedMessageReflection對象.
void protobuf_AssignDesc_xxx_2eproto() {
protobuf_AddDesc_xxx_2eproto();
const ::google::protobuf::FileDescriptor* file =
::google::protobuf::DescriptorPool::generated_pool()->FindFileByName(
"xxx.proto");
GOOGLE_CHECK(file != NULL);
Test_descriptor_ = file->message_type(0);
static const int Test_offsets_[1] = {
//這裏在計算屬性的內存偏移.
GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Test, id_),
};
// 這裏是個test包填裝的GeneratedMessageReflection對象.
Test_reflection_ =
new ::google::protobuf::internal::GeneratedMessageReflection(
Test_descriptor_,
Test::default_instance_,
Test_offsets_,
GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Test, _has_bits_[0]),
GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Test, _unknown_fields_),
-1,
::google::protobuf::DescriptorPool::generated_pool(),
::google::protobuf::MessageFactory::generated_factory(),
sizeof(Test));
}
inline void protobuf_AssignDescriptorsOnce() {
::google::protobuf::GoogleOnceInit(&protobuf_AssignDescriptors_once_,
&protobuf_AssignDesc_xxx_2eproto);
}
// message.h 中 message的基本接口.
virtual const Reflection* GetReflection() const {
return GetMetadata().reflection;
}
// 每個message獲取自己基本信息的接口.
::google::protobuf::Metadata Test::GetMetadata() const {
protobuf_AssignDescriptorsOnce();
::google::protobuf::Metadata metadata;
metadata.descriptor = Test_descriptor_;
metadata.reflection = Test_reflection_;
return metadata;
}
- GeneratedMessageReflection 操作具體對象的屬性
按照offset數組的提示,註解獲取操作對應內存,這裏以int32字段的SetInt32接口爲例子.
- 接口定義
#undef DEFINE_PRIMITIVE_ACCESSORS
#define DEFINE_PRIMITIVE_ACCESSORS(TYPENAME, TYPE, PASSTYPE, CPPTYPE)
void GeneratedMessageReflection::Set##TYPENAME( \
Message* message, const FieldDescriptor* field, \
PASSTYPE value) const { \
USAGE_CHECK_ALL(Set##TYPENAME, SINGULAR, CPPTYPE); \
if (field->is_extension()) { /*先不要在意這個*/ \
return MutableExtensionSet(message)->Set##TYPENAME( \
field->number(), field->type(), value, field); \
} else {
/*一般的字段走這裏*/\
SetField<TYPE>(message, field, value); \
} \
}
DEFINE_PRIMITIVE_ACCESSORS(Int32 , int32 , int32 , INT32 )
#undef DEFINE_PRIMITIVE_ACCESSORS
- 內存賦值.
// 找到對應的內存地址,返回合適類型的指針.
template <typename Type>
inline Type* GeneratedMessageReflection::MutableRaw(
Message* message, const FieldDescriptor* field) const {
int index = field->containing_oneof() ?
descriptor_->field_count() + field->containing_oneof()->index() :
field->index();
void* ptr = reinterpret_cast<uint8*>(message) + offsets_[index];
return reinterpret_cast<Type*>(ptr);
}
// 設置protobuf的標誌bit.
inline void GeneratedMessageReflection::SetBit(
Message* message, const FieldDescriptor* field) const {
if (has_bits_offset_ == -1) {
return;
}
MutableHasBits(message)[field->index() / 32] |= (1 << (field->index() % 32));
}
// 設置某個字段的值
template <typename Type>
inline void GeneratedMessageReflection::SetField(
Message* message, const FieldDescriptor* field, const Type& value) const {
if (field->containing_oneof() && !HasOneofField(*message, field)) {
ClearOneof(message, field->containing_oneof()); // V3 oneof 類型的清理。
}
*MutableRaw<Type>(message, field) = value; // 先直接覆蓋
field->containing_oneof() ?
SetOneofCase(message, field) : SetBit(message, field); // 添加標記bit
}
通過對象和對象方法的名字調用對應的方法.
//TODO