【C++】Google Protocol Buffer(protobuf)详解(二)

代码走读:caffe中protobuf的详细使用过程

【一】proto文件,以caffe.proto中BlobShape为例
syntax = "proto2";	//指明protobuf版本,默认是v2,其它版本:"proto3"

package caffe; 	// 最终生成c++代码: namespace caffe 

message BlobShape {	// 最终生成c++代码: class BlobShape : public ::google::protobuf::Message {
  //下面语句最终生成c++代码: google::protobuf::RepeatedField< ::google::protobuf::int64 > dim_;
  repeated int64 dim = 1 [packed = true];	//repeated列表字段,等号后面数字表示标识符,[packed=true]保证更高效的编码
}
【二】编译命令 protoc -I=. --cpp_out=. caffe.proto
【三】生成的caffe.pb.h
......
namespace caffe {	//对应caffe.proto中 package caffe;
// protobuf内部实现细节——不要调用这些接口
void  protobuf_AddDesc_caffe_2eproto();
void protobuf_AssignDesc_caffe_2eproto();
void protobuf_ShutdownFile_caffe_2eproto();

class BlobShape;
// ===================================================================
class BlobShape : public ::google::protobuf::Message {	//对应caffe.proto中 message BlobShape
 public:
  BlobShape(); // 构造函数
  virtual ~BlobShape(); //析构函数
  BlobShape(const BlobShape& from); // 拷贝构造函数
  inline BlobShape& operator=(const BlobShape& from) { //赋值构造函数
    CopyFrom(from);
    return *this;
  }
  
// ** UnknownFieldSet用于跟踪解析协议消息但其字段编号或类型无法识别时看到的字段。
// 这种情况最常发生在将新字段添加到消息类型中,然后包含这些字段的消息由添加新类型之前编译的旧软件读取。
// 大多数用户永远不需要使用这个类。比如在caffe中就没有使用到
// 参考网址:https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/UnknownFieldSet
  inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
    return _unknown_fields_;
  }

  inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
    return &_unknown_fields_;
  }
  
// ** 描述元数据,主要用在反射上,caffe中没有用到
// 参考网址:http://www.blogjava.net/DLevin/archive/2015/04/01/424012.html
  static const ::google::protobuf::Descriptor* descriptor();
  static const BlobShape& default_instance();

  void Swap(BlobShape* other);	// 交换

  // implements Message 消息实现----------------------------------------------

  BlobShape* New() const;	// 新建
  void CopyFrom(const ::google::protobuf::Message& from);	// 复制
  void MergeFrom(const ::google::protobuf::Message& from);	// 合并:单数字段会被覆盖、Repeated (类似链表)字段会被连接到一起
  void CopyFrom(const BlobShape& from);
  void MergeFrom(const BlobShape& from);
  void Clear();	// 清除消息的所有字段,并将它们设置为默认值。
  bool IsInitialized() const;	// 快速检查是否所有必需字段都设置了值。

  int ByteSize() const;	// 消息的序列化大小
  bool MergePartialFromCodedStream(::google::protobuf::io::CodedInputStream* input);	//从流中读取协议缓冲区并将其合并到此消息中。
  void SerializeWithCachedSizes(::google::protobuf::io::CodedOutputStream* output) const;	//序列化消息而不重新计算大小。
  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;	//序列化到output中
  int GetCachedSize() const { return _cached_size_; }	//返回上次调用ByteSize()的结果
  private:
  void SharedCtor();
  void SharedDtor();
  void SetCachedSize(int size) const;
  public:

  ::google::protobuf::Metadata GetMetadata() const;	// 获取缓冲大小

  // nested types message内嵌的类型 ----------------------------------------------------
  // 如果在proto的mesage中定义
  /* 
  enum SnapshotFormat {
    HDF5 = 0;
    BINARYPROTO = 1;
  }
  */
  // 则生成如下代码:
  /*
  enum SolverParameter_SnapshotFormat {
    SolverParameter_SnapshotFormat_HDF5 = 0,
    SolverParameter_SnapshotFormat_BINARYPROTO = 1
  };
  bool SolverParameter_SnapshotFormat_IsValid(int value);
  const SolverParameter_SnapshotFormat SolverParameter_SnapshotFormat_SnapshotFormat_MIN = SolverParameter_SnapshotFormat_HDF5;
  const SolverParameter_SnapshotFormat SolverParameter_SnapshotFormat_SnapshotFormat_MAX = SolverParameter_SnapshotFormat_BINARYPROTO;
  const int SolverParameter_SnapshotFormat_SnapshotFormat_ARRAYSIZE = SolverParameter_SnapshotFormat_SnapshotFormat_MAX + 1;
  
  typedef SolverParameter_SnapshotFormat SnapshotFormat;
  static const SnapshotFormat HDF5 = SolverParameter_SnapshotFormat_HDF5;
  static const SnapshotFormat BINARYPROTO = SolverParameter_SnapshotFormat_BINARYPROTO;
  static inline bool SnapshotFormat_IsValid(int value) {
    return SolverParameter_SnapshotFormat_IsValid(value);
  }
  static const SnapshotFormat SnapshotFormat_MIN = SolverParameter_SnapshotFormat_SnapshotFormat_MIN;
  static const SnapshotFormat SnapshotFormat_MAX = SolverParameter_SnapshotFormat_SnapshotFormat_MAX;
  static const int SnapshotFormat_ARRAYSIZE = SolverParameter_SnapshotFormat_SnapshotFormat_ARRAYSIZE;
  static inline const ::google::protobuf::EnumDescriptor*
  SnapshotFormat_descriptor() {
    return SolverParameter_SnapshotFormat_descriptor();
  }
  static inline const ::std::string& SnapshotFormat_Name(SnapshotFormat value) {
    return SolverParameter_SnapshotFormat_Name(value);
  }
  static inline bool SnapshotFormat_Parse(const ::std::string& name, SnapshotFormat* value) {
    return SolverParameter_SnapshotFormat_Parse(name, value);
  }
  */
  
  // accessors -------------------------------------------------------

  // repeated int64 dim = 1 [packed = true]; // 对 dim 成员的操作
  inline int dim_size() const;	// 返回dim 列表大小
  inline void clear_dim();	// 清空
  static const int kDimFieldNumber = 1;	// *FieldNumber 的值对应 dim = 1 标识1
  inline ::google::protobuf::int64 dim(int index) const;	// 获取列表dim中指定索引的值
  inline void set_dim(int index, ::google::protobuf::int64 value); // 设置列表dim指定索引的值
  inline void add_dim(::google::protobuf::int64 value);	// 添加新元素到列表dim中
  inline const ::google::protobuf::RepeatedField< ::google::protobuf::int64 >& dim() const; // 返回dim的const引用,不可修改
  inline ::google::protobuf::RepeatedField< ::google::protobuf::int64 >*  mutable_dim();	// 返回dim的指针,可修改

  // @@protoc_insertion_point(class_scope:caffe.BlobShape)
 private:

  ::google::protobuf::UnknownFieldSet _unknown_fields_;

  ::google::protobuf::RepeatedField< ::google::protobuf::int64 > dim_;
  mutable int _dim_cached_byte_size_;

  mutable int _cached_size_;
  ::google::protobuf::uint32 _has_bits_[(1 + 31) / 32];	// 1表示 message BlobShape中定义的成员个数为1

  friend void  protobuf_AddDesc_caffe_2eproto();
  friend void protobuf_AssignDesc_caffe_2eproto();
  friend void protobuf_ShutdownFile_caffe_2eproto();

  void InitAsDefaultInstance();
  static BlobShape* default_instance_;
};
// ===================================================================
// BlobShape对应的操作
// repeated int64 dim = 1 [packed = true];
inline int BlobShape::dim_size() const { //dim是列表形式,因此提供返回大小的接口
  return dim_.size();
}
inline void BlobShape::clear_dim() { // 清空
  dim_.Clear();
}
inline ::google::protobuf::int64 BlobShape::dim(int index) const { //获取指定索引的值
  return dim_.Get(index);
}
inline void BlobShape::set_dim(int index, ::google::protobuf::int64 value) { //设置指定索引的值
  dim_.Set(index, value);
}
inline void BlobShape::add_dim(::google::protobuf::int64 value) { //增量添加值到列表dim中
  dim_.Add(value);
}
inline const ::google::protobuf::RepeatedField< ::google::protobuf::int64 >&
BlobShape::dim() const { //返回只读dim
  return dim_;
}
inline ::google::protobuf::RepeatedField< ::google::protobuf::int64 >*
BlobShape::mutable_dim() { //返回可读写dim
  return &dim_;
}
......
【四】生成的caffe.pb.cc

【五】官网说明

谷歌官网说明:https://developers.google.com/protocol-buffers/
c++API手册:https://developers.google.com/protocol-buffers/docs/reference/cpp/#google.protobuf

【六】使用
1、将文本格式的 protobuf 读取到 Message 中
1.1 以 caffe 中 ReadProtoFromTextFile 为例

ReadProtoFromTextFile的调用步骤:
train() --> caffe::ReadSolverParamsFromTextFileOrDie(FLAGS_solver, &solver_param); --> ReadProtoFromTextFile(param_file, param)

1.2、ReadProtoFromTextFile 源码详解
bool ReadProtoFromTextFile(const char* filename, Message* proto) {
  int fd = open(filename, O_RDONLY);
  CHECK_NE(fd, -1) << "File not found: " << filename;
  FileInputStream* input = new FileInputStream(fd);
  bool success = google::protobuf::TextFormat::Parse(input, proto);
  delete input;
  close(fd);
  return success;
}

1.2.1 FileInputStream 详解
官方说明:

FileInputStream 继承自 ZeroCopyInputStream(从文件描述符读取流)

使用方法:(参考构建函数)

explicit FileInputStream(int file_descriptor, int block_size = -1);

参数:

	file_descriptor : Unix 文件描述符
	block_size:指定 Next() 时读取和返回的大小,不指定时,将使用一个合理的默认值。

1.2.2 google::protobuf::TextFormat::Parse
TextFormat 类:

该类实现 protobuf 文本格式。以文本格式打印和解析协议消息对于调试和人工编辑消息非常有用。
这个类实际上是一个只包含静态方法的命名空间。

静态函数 Parse:

从给定的输入流解析 文本格式协议消息 到给定的 消息对象。这个函数解析Print()编写的格式。
static bool Parse(io::ZeroCopyInputStream* input, Message* output);

其它类似静态函数:

static bool ParseFromString(const string& input, Message* output);  // 直接从字符串解析
static bool Merge(io::ZeroCopyInputStream* input, Message* output); // 从流中解析,并合并到 Message 中,参见 Message::MergeFrom()
static bool MergeFromString(const string& input, Message* output);  // 同上,但是直接从字符串解析

//解析单个字段值到指定的 message 的指定 field 字段中
static bool ParseFieldValueFromString(const string& input, const FieldDescriptor* field, Message* message); 

对应的反操作:将 Message 打印到流或文本中

static bool Print(const Message& message, io::ZeroCopyOutputStream* output); // 将 message 打印到输出流中
static bool PrintToString(const Message& message, string* output);	// 将 message 打印到字符串中

// 将 message 指定的 field 字段,输出到字符串中。字段是列表格式时,指定引索 index;非列表格式时 index 必须指定为 -1
static void PrintFieldValueToString(const Message& message, const FieldDescriptor* field, int index, string* output); 

// 打印未知数集 UnknownFieldSet 中的字段。它们只按标签号打印。通过尝试解析嵌入的消息,可以直观地识别它们。
static bool PrintUnknownFields(const UnknownFieldSet& unknown_fields, io::ZeroCopyOutputStream* output); 
2、将 message 序列化到二进制文件中
2.1 以 caffe 中 WriteProtoToBinaryFile 函数为例
void WriteProtoToBinaryFile(const Message& proto, const char* filename) {
  fstream output(filename, ios::out | ios::trunc | ios::binary);
  CHECK(proto.SerializeToOstream(&output));	
  // 原型bool Message::SerializeToOstream(ostream* output) const; 将 Message 序列化到c++标准输出流中,必须设置所有必需字段。
}
2.2 序列化函数汇总:
// 将 Message 序列化到文件描述符中,必须设置所有必需字段。
bool SerializeToFileDescriptor(int file_descriptor) const;	

// 同上,但是允许缺少必需的字段。	
bool SerializePartialToFileDescriptor(int file_descriptor) const;

// 将 Message 序列化到c++标准输出流中,必须设置所有必需字段。
bool SerializeToOstream(ostream* output) const;	

// 同上,但是允许缺少必需的字段。		
bool SerializePartialToOstream(ostream* output) const; 	
3、从二进制文件中 反序列化 到 message 中
3.1 以 caffe 中 ReadProtoFromBinaryFile 为例
bool ReadProtoFromBinaryFile(const char* filename, Message* proto) {
  int fd = open(filename, O_RDONLY);
  CHECK_NE(fd, -1) << "File not found: " << filename;
  ZeroCopyInputStream* raw_input = new FileInputStream(fd);
  CodedInputStream* coded_input = new CodedInputStream(raw_input);
  coded_input->SetTotalBytesLimit(kProtoReadBytesLimit, 536870912);

  bool success = proto->ParseFromCodedStream(coded_input);	
  // 原型 bool ParseFromCodedStream(io::CodedInputStream* input);

  delete coded_input;
  delete raw_input;
  close(fd);
  return success;
}
3.2 其它反序列化函数:
bool ParsePartialFromCodedStream(io::CodedInputStream* input);	// 同 ParseFromCodedStream ,但是接受缺少必需字段的消息。
bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);		// 从 io::ZeroCopyInputStream 流中获取
bool ParsePartialFromZeroCopyStream(io::ZeroCopyInputStream* input); // 同上,但是接受缺少必需字段的消息。
bool ParseFromBoundedZeroCopyStream(io::ZeroCopyInputStream* input, int size); // 同上,指定 message 大小必须是 size
bool ParsePartialFromBoundedZeroCopyStream(io::ZeroCopyInputStream* input, , int size); // 同上,但是接受缺少必需字段的消息。

你可能感兴趣的:(C++,c++,caffe)