一、為什么需要extension
Protobuf的文檔明確說明了禁止繼承protobuf的消息,而且在生成的C++消息中也添加了final來從語法上完全禁止繼承這些消息。protobuf把這些說明放在序列化和反序列化這個條目下,可能主要是基於序列化/反序列化的處理。但是在某些情況下,如果我們一定要擴展某個消息該如何處理呢?
Protocol Buffers and O-O Design Protocol buffer classes are basically dumb data holders (like structs in C); they don't make good first class citizens in an object model. If you want to add richer behaviour to a generated class, the best way to do this is to wrap the generated protocol buffer class in an application-specific class. Wrapping protocol buffers is also a good idea if you don't have control over the design of the .proto file (if, say, you're reusing one from another project). In that case, you can use the wrapper class to craft an interface better suited to the unique environment of your application: hiding some data and methods, exposing convenience functions, etc. You should never add behaviour to the generated classes by inheriting from them. This will break internal mechanisms and is not good object-oriented practice anyway.
這個時候,其實可以考慮使用extension這個功能,這種功能從本質上看就是對於一些已經存在並且聲明了允許擴展的消息進行擴展。當然這種擴展只是動態的增加了字段,而沒有增加接口,所以它通常是作為一些選項(option)來使用。
這種情況通常存在於框架層,比方說框架層接口接收的參數是一個Message對象,但是希望這些不同的Message根據不同的選項具有不同的行為,測試框架層可以定義一個對某種消息的擴展,然后通過擴展的接口來獲得不同消息的具體值。
當然也可以把這些擴展直接定義到框架消息中去,但是就會使框架消息不斷膨脹,從而不利於隔離。例如大致是這種情況
message basemsg
{
int32 modAint = 1;
……
int64 modBlong = 100;
};
這樣看起來基類就很臃腫,不同模塊的成員全部放在一起,每次添加一個新的模塊,或者某個模塊添加一個新的字段,這個結構都要重新生成,其它模塊都要重新構建一下。
相反,使用extension就可以做到相互的隔離:
框架類基礎消息
message basemsg
{
extensions 1 to 100;
};
modA.proto文件中
extend basemsg
{
int32 modAint = 1;
};
modB.proto文件中
extend basemsg
{
int64 modBint = 100;
};
這樣基類的消息不用修改,而且不同模塊添加字段也不受影響。
下面是一個例子
tsecer@harry: protoc -I . --cpp_out=. ModB.proto
tsecer@harry: cat basemsg.proto
syntax = "proto2";
message basemsg
{
required int32 baseint = 1;
extensions 100 to 200;
};
tsecer@harry: cat ModA.proto
syntax = "proto2";
import "basemsg.proto";
extend basemsg
{
optional int32 ModAint = 100;
};
tsecer@harry: cat ModB.proto
syntax = "proto2";
import "basemsg.proto";
extend basemsg
{
optional int64 ModBint = 101;
};
tsecer@harry: protoc -I . --cpp_out=. Mod*.proto
tsecer@harry:
二、proto3中對option擴展的使用
注意前面使用的是proto2的語法,因為在proto3中只支持對於option的extend。由於這個原因,那就只能看下option在proto3中的使用了。
tsecer@harry: cat myoption.proto
syntax = "proto3";
import "google/protobuf/descriptor.proto";
message mymessage
{
int32 x = 10;
};
extend google.protobuf.MessageOptions {
mymessage mymsg = 51234;
}
message usemymessage
{
option(mymsg) = {x : 1000 };
int32 xxx = 11;
};
tsecer@harry: cat main.cpp
#include "myoption.pb.h"
#include "stdio.h"
int main(int argc, const char *argv[])
{
usemymessage usemm;
printf("%d\n", usemm.GetDescriptor()->options().GetExtension(mymsg).x());
}
tsecer@harry: g++ main.cpp myoption.pb.cc -std=c++11 -l protobuf
tsecer@harry: ./a.out
1000
tsecer@harry:
三、mymsg是什么
從生成的頭文件看
static const int kMymsgFieldNumber = 51234;
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::google::protobuf::MessageOptions,
::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::mymessage >, 11, false >
mymsg;
從生成的cc文件看
::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::google::protobuf::MessageOptions,
::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::mymessage >, 11, false >
mymsg(kMymsgFieldNumber, *::mymessage::internal_default_instance());
也就是說,它是一個類型為
ExtensionIdentifier< ::google::protobuf::MessageOptions,
::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::mymessage >, 11, false >
的全局變量。這個變量類型其實比較復雜,作用在於當這個變量作為模板函數參數的時候可以自動還原(推導)出和這個變量綁定的所有類型。
四、如何獲得擴展的值
protobuf-master\src\google\protobuf\descriptor.pb.h
class PROTOBUF_EXPORT MessageOptions final :
public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:google.protobuf.MessageOptions) */ {
……
void set_map_entry(bool value);
GOOGLE_PROTOBUF_EXTENSION_ACCESSORS(MessageOptions)
// @@protoc_insertion_point(class_scope:google.protobuf.MessageOptions)
private:
……
}
GOOGLE_PROTOBUF_EXTENSION_ACCESSORS宏的定義(其實其它FileOption、FieldOption等都有這個宏,這也是這個接口定義為宏的原因:為了讓多個類都可以使用),可以看到,它的主要功能就是通過模板函數推導出原始類型作為返回類型,使用變量中保存的id來從集合中查找到對應的值。
protobuf-master\src\google\protobuf\extension_set.h
// -------------------------------------------------------------------
// Generated accessors
// This macro should be expanded in the context of a generated type which
// has extensions.
//
// We use "_proto_TypeTraits" as a type name below because "TypeTraits"
// causes problems if the class has a nested message or enum type with that
// name and "_TypeTraits" is technically reserved for the C++ library since
// it starts with an underscore followed by a capital letter.
//
// For similar reason, we use "_field_type" and "_is_packed" as parameter names
// below, so that "field_type" and "is_packed" can be used as field names.
#define GOOGLE_PROTOBUF_EXTENSION_ACCESSORS(CLASSNAME) \
……
/* Singular accessors */ \
template <typename _proto_TypeTraits, \
::PROTOBUF_NAMESPACE_ID::internal::FieldType _field_type, \
bool _is_packed> \
inline typename _proto_TypeTraits::Singular::ConstType GetExtension( \
const ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< \
CLASSNAME, _proto_TypeTraits, _field_type, _is_packed>& id) const { \
return _proto_TypeTraits::Get(id.number(), _extensions_, \
id.default_value()); \
} \
……
五、不同類型的擴展如何放在同一個set中
本質上是一種暴力窮舉的方法,將所有可能的類型放入一個union中,從而可以包羅萬象。
protobuf-master\src\google\protobuf\extension_set.h
struct Extension {
// The order of these fields packs Extension into 24 bytes when using 8
// byte alignment. Consider this when adding or removing fields here.
union {
int32 int32_value;
int64 int64_value;
uint32 uint32_value;
uint64 uint64_value;
float float_value;
double double_value;
bool bool_value;
int enum_value;
std::string* string_value;
MessageLite* message_value;
LazyMessageExtension* lazymessage_value;
RepeatedField<int32>* repeated_int32_value;
RepeatedField<int64>* repeated_int64_value;
RepeatedField<uint32>* repeated_uint32_value;
RepeatedField<uint64>* repeated_uint64_value;
RepeatedField<float>* repeated_float_value;
RepeatedField<double>* repeated_double_value;
RepeatedField<bool>* repeated_bool_value;
RepeatedField<int>* repeated_enum_value;
RepeatedPtrField<std::string>* repeated_string_value;
RepeatedPtrField<MessageLite>* repeated_message_value;
};
typedef std::map<int, Extension> LargeMap;