在打印proto时,总需要将pb文件加入源代码,然后编译,然后再解析,实在太麻烦。
能不能直接根据proto文件直接输出明文消息呢?实际上是可以的!
void GetMessageTypeFromProtoFile(
const std::string& proto_filename,
google::protobuf::FileDescriptorProto* file_desc_proto) {
using namespace google::protobuf;
using namespace google::protobuf::io;
using namespace google::protobuf::compiler;
FILE* proto_file = fopen(proto_filename.c_str(), "r");
{
if (proto_file == NULL) {
LOG(FATAL) << "Cannot open .proto file: " << proto_filename;
}
FileInputStream proto_input_stream(fileno(proto_file));
Tokenizer tokenizer(&proto_input_stream, NULL);
Parser parser;
if (!parser.Parse(&tokenizer, file_desc_proto)) {
LOG(FATAL) << "Cannot parse .proto file:" << proto_filename;
}
}
fclose(proto_file);
// Here we walk around a bug in protocol buffers that
// |Parser::Parse| does not set name (.proto filename) in
// file_desc_proto.
if (!file_desc_proto->has_name()) {
file_desc_proto->set_name(proto_filename);
}
}
这个函数的输入是一个 .proto 文件的文件名。输出是一个 FileDescriptorProto 对象。这个对象里存储着对 .proto 文件解析之后的结果。我们接下来用这些结果动态生成某个 protocol message 的 instance(或者用C++术语叫做object)。然后可以调用这个 instance 自己的 ParseFromArray/String 成员函数,来解析数据文件中的每一条记录的内容。请看如下代码:
//-----------------------------------------------------------------------------
// Print contents of a record file with following format:
//
// { <int record_size> <KeyValuePair> }
//
// where KeyValuePair is a proto message defined in mpimr.proto, and
// consists of two string fields: key and value, where key will be
// printed as a text string, and value will be parsed into a proto
// message given as |message_descriptor|.
//-----------------------------------------------------------------------------
void PrintDataFile(
const std::string& data_filename,
const google::protobuf::FileDescriptorProto& file_desc_proto,
const std::string& message_name) {
const int kMaxRecieveBufferSize = 32 * 1024 * 1024; // 32MB
static char buffer[kMaxRecieveBufferSize];
std::ifstream input_stream(data_filename.c_str());
if (!input_stream.is_open()) {
LOG(FATAL) << "Cannot open data file: " << data_filename;
}
google::protobuf::DescriptorPool pool;
const google::protobuf::FileDescriptor* file_desc = pool.BuildFile(file_desc_proto);
if (file_desc == NULL) {
LOG(FATAL) << "Cannot get file descriptor from file descriptor"
<< file_desc_proto.DebugString();
}
const google::protobuf::Descriptor* message_desc =
file_desc->FindMessageTypeByName(message_name);
if (message_desc == NULL) {
LOG(FATAL) << "Cannot get message descriptor of message: " << message_name;
}
google::protobuf::DynamicMessageFactory factory;
const google::protobuf::Message* prototype_msg = factory.GetPrototype(message_desc);
if (prototype_msg == NULL) {
LOG(FATAL) << "Cannot create prototype message from message descriptor";
}
google::protobuf::Message* mutable_msg = prototype_msg->New();
if (mutable_msg == NULL) {
LOG(FATAL) << "Failed in prototype_msg->New(); to create mutable message";
}
uint32_t proto_msg_size; // uint32 is the type used in reocrd files.
for (;;) {
input_stream.read((char*)&proto_msg_size, sizeof(proto_msg_size));
if (proto_msg_size > kMaxRecieveBufferSize) {
LOG(FATAL) << "Failed to read a proto message with size = " << proto_msg_size
<< ", which is larger than kMaxRecieveBufferSize (" << kMaxRecieveBufferSize
<< ")."
<< "You can modify kMaxRecieveBufferSize defined in " << __FILE__;
}
input_stream.read(buffer, proto_msg_size);
if (!input_stream)
break;
if (!mutable_msg->ParseFromArray(buffer, proto_msg_size)) {
LOG(FATAL) << "Failed to parse value in KeyValuePair:" << pair.value();
}
std::cout << mutable_msg->DebugString();
}
delete mutable_msg;
}
这个函数需要三个输入:
1)数据文件的文件名
2)之前GetMessageTypeFromProtoFile函数返回的FileDescriptorProto对象
3)数据文件中每条记录的对应的protocol message 的名字(注意,一个 .proto 文件里可以定义多个 protocol messages,所以我们需要知道数据记录对应的具体是哪一个 message)。
以上代码中利用了 DescriptorPool 从 FileDescriptorProto 解析出 FileDescriptor(描述 .proto 文件中所有的 messages)。然后用 DynamicMessageFactory 从 FileDescriptor 里找到我们关注的那个 message 的 MessageDescriptor。接下来,我们利用 DynamicMessageFactory 根据 MessageDescriptor 得到一个 prototype message instance。注意,这个 instance 是不能往里面写内容的(immutable)。我们需要调用其 New 成员函数,来生成一个 mutable 的 instance。
有了一个对应数据记录的 message instance,接下来就好办了。我们读取数据文件中的每条记录。注意:此处我们假设数据文件中以此存放了一条记录的长度,然后是记录内容,接下来是第二条记录的长度和内容,以此类推。所以在上述函数中,我们循环的读取记录长度,然后解析记录内容。值得注意的是,解析内容利用的是 mutable message instance 的 ParseFromArrary 函数;它需要知道记录的长度。因此我们必须在数据文件中存储每条记录的长度。
接下来这段程序演示如何调用 GetMessageTypeFromProtoFile 和 PrintDataFile:
int main(int argc, char** argv) { std::string proto_filename, message_name; std::vector<string> data_filenames; ::google::protobuf::FileDescriptorProto file_desc_proto; ParseCmdLine(argc, argv, &proto_filename, &message_name, &data_filenames); GetMessageTypeFromProtoFile(proto_filename, &file_desc_proto); for (int i = 0; i < data_filenames.size(); ++i) { PrintDataFile(data_filenames[i], file_desc_proto, message_name); } return 0; }