Protobuf C++ API 简介

本文是一篇译文,原文为C++ API

descriptor.h

#include 
namespace google::protobuf

这个文件包含了描述protocol message类型信息的类。

关键类有:

  • Descriptor:描述protocol message的类型信息
  • FieldDescriptor:描述message的某个字段
  • DescriptorPool:创建desciptors

你可以使用消息的desciptor,从而在运行时了解该消息包含哪些字段以及这些字段的类型。 Message接口还允许您通过传递您感兴趣的字段的FieldDescriptor来动态访问和修改单个字段。

class Descriptor

#include 
namespace google::protobuf

descriptor描述了protocol message的类型信息。可以通过调用Message::GetDescriptor()来获取给定的消息对象的描述符。编译器protoc产生的消息类也有一个名为descriptor()的静态函数可以返回该消息的描述符。我们也可以用DescriptorPool来创建自己的描述符。

Descriptor的主要函数有:

成员函数 描述
const string & name() const 消息的类型名,不包含它的作用域
const string & full_name() const 消息类型的完全限定名称,如bar.Foo.Baz
int index() const 该描述符在文件或包含类型的消息类型数组中的索引。
const MessageOptions & options() const 获取此消息类型的选项。
int field_count() const 此消息类型中的字段数。
const FieldDescriptor * field(int index) const 按索引获取字段,其中0 <= index 它们按照.proto文件中定义的顺序返回。
const FieldDescriptor *
FindFieldByNumber(int number) const
按声明的标识号查找字段。如果不存在此类字段,则返回NULL。
const FieldDescriptor *
FindFieldByName(const string & name) const
按名称查找字段。 如果不存在此类字段,则返回NULL。

class FieldDescriptor

#include 
namespace google::protobuf

FieldDescriptor描述了message的某个字段。为了获取给定字段的描述符,首先获取定义该字段的消息的描述符,然后调用Descriptor::FindFieldByName()

class DescriptorPool

#include 
namespace google::protobuf

用来创建描述符。

通常情况下,我们不希望构建自己的描述符。协议编译器构造的消息类会为我们提供。但是,如果我们正在自己实现Message,或者我们正在编写一个可以运行于任意数据类型上并且需要从某种数据库加载这些数据类型的程序,我们可能就需要构建自己的描述符。

由于描述符由大量交叉链接的数据组成,这些数据很难手动组合在一起,因此提供了DescriptorPool以简化过程。它可以采用FileDescriptorProto(在descriptor.proto中定义),验证它,并将其转换为一组很好的交叉链接描述符。

DescriptorPool还有助于内存管理。描述符由许多包含静态数据和指针的对象组成。当需要删除此数据时,我们很可能需要立即删除所有数据。实际上,我们希望立即删除的池中的描述符全部都是交叉链接的情况并不少见。DescriptorPool类就是这样的描述符池,它会为我们处理内存管理。

我们还可以按名称搜索DescriptorPool中的描述符,或者按编号搜索扩展名。

成员函数 描述
static const DescriptorPool *
generated_pool()
获取指向生成的描述符池的指针。
生成的协议消息类将在此池中分配其描述符。 不要将自己的描述符添加到此池中。
const Descriptor *
FindMessageTypeByName(const string & name) const
按完全限定名称查找描述符。 会查找顶级描述符和嵌套描述符。 如果找不到,则返回NULL。
const FieldDescriptor *
FindFieldByName(const string & name) const
按完全限定名称查找描述符。 会查找顶级描述符和嵌套描述符。 如果找不到,则返回NULL。

给出一个使用desciptor的例子。假设有一个消息的定义为:

message Foo {
  optional string text = 1;
  repeated int32 numbers = 2;
}

然后我们用protoc编译器生成了相应的类,我们可以这样使用该类:

string data;  // Will store a serialized version of the message.

{
  // Create a message and serialize it.
  Foo foo;
  foo.set_text("Hello World!");
  foo.add_numbers(1);
  foo.add_numbers(5);
  foo.add_numbers(42);

  foo.SerializeToString(&data);
}

{
  // Parse the serialized message and check that it contains the
  // correct data.
  Foo foo;
  foo.ParseFromString(data);

  assert(foo.text() == "Hello World!");
  assert(foo.numbers_size() == 3);
  assert(foo.numbers(0) == 1);
  assert(foo.numbers(1) == 5);
  assert(foo.numbers(2) == 42);
}

{
  // Same as the last block, but do it dynamically via the Message
  // reflection interface.
  Message* foo = new Foo;
  const Descriptor* descriptor = foo->GetDescriptor();

  // Get the descriptors for the fields we're interested in and verify
  // their types.
  const FieldDescriptor* text_field = descriptor->FindFieldByName("text");
  assert(text_field != NULL);
  assert(text_field->type() == FieldDescriptor::TYPE_STRING);
  assert(text_field->label() == FieldDescriptor::LABEL_OPTIONAL);
  const FieldDescriptor* numbers_field = descriptor->
                                         FindFieldByName("numbers");
  assert(numbers_field != NULL);
  assert(numbers_field->type() == FieldDescriptor::TYPE_INT32);
  assert(numbers_field->label() == FieldDescriptor::LABEL_REPEATED);

  // Parse the message.
  foo->ParseFromString(data);

  // Use the reflection interface to examine the contents.
  const Reflection* reflection = foo->GetReflection();
  assert(reflection->GetString(*foo, text_field) == "Hello World!");
  assert(reflection->FieldSize(*foo, numbers_field) == 3);
  assert(reflection->GetRepeatedInt32(*foo, numbers_field, 0) == 1);
  assert(reflection->GetRepeatedInt32(*foo, numbers_field, 1) == 5);
  assert(reflection->GetRepeatedInt32(*foo, numbers_field, 2) == 42);

  delete foo;
}

message_lite.h

#include 
namespace google::protobuf

定义了类MessageLite------轻量级的protocol message的抽象接口类,它是Message类的父类,实现了除了与descriptor和reflection相关的接口以外的所有其它接口。如果欠想让protocol编译器只生成由MessageLite实现的类,可以在.proto文件中添加语句option optimize_for = LITE_RUNTIME;

这在资源受限的系统中特别有用,完整的protocol buffer运行时库太大了。

请注意,在非约束系统(例如服务器)上如果需要链接许多协议定义,一种更好的减少方法
总代码占用量是使用optimize_for = CODE_SIZE。 这个将使生成的代码更小,同时仍然支持所有相同的功能(以速度为代价)。 如果只有少量的消息类型需要链接,这时protocol buffer的运行时是主要问题,这时我们可以使用optimize_for = LITE_RUNTIME

这里介绍一些常用的接口:

成员函数 描述
virtual string GetTypeName() const = 0 获取message的类型名,如“foo.bar.BarzProto"(纯虚函数)
virtual MessageLite * New() const = 0 构造一个与本对象相同类型的新实例
virtual void SerializeWithCachedSizes(io::CodedOutputStream * output) const = 0 序列化消息而不重新计算大小。自上次调用ByteSize()以来,消息不得更改,ByteSize返回的值必须为非负数。 否则结果是不确定的。
virtual uint8 * SerializeWithCachedSizesToArray(uint8 * target) const 序列化消息,但不保证能完全序列化
bool ParseFromArray(const void * data, int size) 解析包含在字节数组中的Protobuf

message.h

#include 
namespace google::protobuf

定义了Message类-----非轻量级的protocol message对象的抽象接口类。

struct Metadata

#include 
namespace google::protobuf

Metadata结构体保存了message 的元数据:descriptor和reflection

class Message: public MessageLite

#include 
namespace google::protobuf

Message类在MessageLite类的基础上添加了descriptors和reflection(反射)。

这里介绍一些常用的接口:

Basic Operations 描述
virtual Message * New() const = 0 构造一个与本对象相同类型的新实例
(MessageLite也定义了这个接口,返回值不同)
virtual void Message::CopyFrom(
const Message & from)
将本对象变为给定message的一个拷贝。给定的message必须有相同的descriptor,但不必是相同的类。默认情况,该接口的内部实现为 “Clear(); MergeFrom(from);”
virtual void Message::MergeFrom(
const Message & from)
将给定消息中的字段合并到此消息中 。
virtual int Message::SpaceUsed() const 计算(估计)当前消息在内存中占的总字节数。
string DebugString() const 生成此消息的可读形式,可用于调试和其他目的。

IO接口 描述
bool ParseFromFileDescriptor(
int file_descriptor)
从文件描述符中解析protocol buffer。如果成功,会解析整个输入
bool ParsePartialFromFileDescriptor(
int file_descriptor)
与ParseFromFileDescriptor相似,但接受缺少required字段的message
bool Message::ParseFromIstream(
std::istream * input)
从C++ istream中解析protocol buffer。如果成功,会解析整个输入
bool Message::ParsePartitialFromIstream(
std::istream * input)
与ParseFromIstream相似,但接受缺少required字段的message
bool Message::SerializeToFileDescriptor(
int file_descriptor) const
序列化message并将其写入给定的文件描述符。所有required字段都必须被初始化
bool Message::SerializePartialToFileDescriptor(
int file_descriptor) const
与SerializeToFileDescriptor相似,但允许缺少required字段
bool Message::SerializeToOstream(
std::ostream * output) const
序列化message并将其写入给定的C++ ostream。所有required字段都必须被初始化
bool Message::SerializePartialToOstream(
std::ostream * output) const
与SerializeToOstream相似,但允许缺少required字段
基于反射的方法 描述
virtual string GetTypeName() const 获取message的类型名,如“foo.bar.BarzProto"
virtual void Message::Clear() 清空message的所有字段,并将所有字段值设为相应的默认值。Clear()并没有释放内存,它假设已分配的内存会被再次使用。如果需要释放消息占用的内存,必须将该消息删除。
virtual bool IsInitialized() const 快速检查是否所有required字段的值有被设置值
virtual void Message::CheckTypeAndMergeFrom(
const MessageLite & other)
如果other与本对象是完全相同的类,则会调用MerageFrom()。否则结果未定义(可能程序会崩溃)
virtual size_t Message::ByteSizeLong() const 计算消息序列化后的大小
const Descriptor *
GetDescriptor() const
返回本消息类型的descriptor,它描述了消息包含的哪些字段及字段的类型
virtual const Reflection *
Message::GetReflection() const
返回本对象的反射接口,它可以用来动态获取和修改消息的字段,也就是说,我们不需要在编译期知道消息的类型

class Reflection

#include 
namespace google::protobuf

此接口包含可用于动态访问和修改协议消息字段的方法。

仅仅是出于效率原因,才会将此接口与Message分离; Message的绝大多数实现将共享同一Reflection(GeneratedMessageReflection,在generated_message.h中定义),并且特定类的所有消息应共享相同的Reflection对象。

有几种错误使用该接口的情况,以下任何一种情况都会导致未定义的结果(可能是断言失败):

  • FieldDescriptor不是此消息类型的字段。
  • 调用的方法不适合字段的类型。 对于FieldDescriptor :: TYPE_ *中的每个字段类型,只有一个Get *()方法,一个Set *()方法和一个对该类型有效的Add *()方法。 应该很明显(除了TYPE_BYTES,在C ++中使用字符串表示)。
  • 在required字段上调用singular字段的Get *()或Set *()方法。
  • 在非required字段上调用GetRepeated *(),SetRepeated *()或Add *()。
  • 传递给某一函数的Message对象不是此Reflection对象的正确类型(即message.GetReflection()!= reflection)。

具体的相关接口的信息请查看https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#Message.New.details

class MessageFactory

#include 
namespace google::protobuf

MessageFactory是一个用于创建消息对象的抽象接口。其主要函数有:

virtual const Message *	GetPrototype(const Descriptor * type) = 0

基于给定的描述符,获取或构造该类型的默认(原型)消息。对于每个具体的消息类型都有一个default_instance,可以通过调用ConcreteMessage::default_instance()来获得。

然后,我们可以调用该消息的New()方法来构造该类型的mutable消息。

使用相同的Descriptor调用此方法两次将返回相同的对象。 此外,通过调用原型的New()函数创建的任何对象都会与其原型共享一些数据,因此必须在销毁MessageFactory之前销毁这些对象。

给定的描述符必须比返回的消息生命期更长,因此也必须比MessageFactory的生命期更长。

某些实现不支持所有类型。 如果不支持传入的描述符,则GetPrototype()将返回NULL。


static MessageFactory * MessageFactory::generated_factory()

获取一个MessageFactory,它支持所有生成的已编译消息。换句话说,对于FooMessage的任意编译类型,以下情况都将返回true

MessageFactory::generated_factory()->GetPrototype(FooMessage::descriptor()) 
== FooMessage::default_instance()

生成的MessageFactory对象支持DescriptorPool :: generated_pool()中的所有类型。 如果从其他描述符池中获取描述符,则GetPrototype()将返回NULL。 我们还可以通过descriptor->file()->pool() == DescriptorPool::generated_pool()来检查描述符是否对应于一个生成的消息。

该factory是线程安全的,调用GetPrototype()不会修改任何共享数据。并且它是单例模式,调用者不能删除该对象。

你可能感兴趣的:(Protobuf)