C++打印任意proto消息方法

在打印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&#91;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;
}
赞赏

微信赞赏支付宝赞赏

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注