proto3中option extension的用法

一、为什么需要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;

posted on 2020-09-10 19:14  tsecer  阅读(11810)  评论(0编辑  收藏  举报

导航