C++實用技術 - protobuf動態解析proto

原理

利用protobuf的反射功能,我們可以解析任意message的任意字段信息。
有時候我們可能需要動態加載proto文件,利用新加載進來的proto文件的信息去解析數據。並把數據輸出或者轉格式
這時候我們就需要利用到protobuf的google::protobuf::compiler::DiskSourceTree類去加載proto文件。
利用google::protobuf::compiler::Importer類去導入proto文件

實現代碼

測試用的proto文件

//sylar.proto
package sylar;

message AA {
    optional string name = 1;
    optional int32 age = 2;
}

message Test {
    optional string name = 1;
    optional int32 age = 2;
    repeated string phones = 3;
    repeated AA aa = 4;
}

message XX {
    optional string name = 1;
    repeated AA aa = 4;
}

動態解析proto文件代碼

int DynamicParseFromPBFile(const std::string& filename
    , const std::string& classname
    , std::function<void(::google::protobuf::Message* msg)> cb) {
    //TODO 檢查文件名是否合法
    auto pos = filename.find_last_of('/');
    std::string path;
    std::string file;
    if(pos == std::string::npos) {
        file = filename;
    } else {
        path = filename.substr(0, pos);
        file = filename.substr(pos + 1);
    }

    ::google::protobuf::compiler::DiskSourceTree sourceTree;
    sourceTree.MapPath("", path);
    ::google::protobuf::compiler::Importer importer(&sourceTree, NULL);
    importer.Import(file);
    const ::google::protobuf::Descriptor *descriptor
        = importer.pool()->FindMessageTypeByName(classname);
    if(!descriptor) {
        return 1;
    }
    ::google::protobuf::DynamicMessageFactory factory;
    const ::google::protobuf::Message *message
        = factory.GetPrototype(descriptor);
    if(!message) {
        return 2;
    }
    ::google::protobuf::Message* msg = message->New();
    if(!msg) {
        return 3;
    }
    cb(msg);
    delete msg;
    return 0;
}

動態解析proto string

int DynamicParseFromPBString(const std::string& proto_string
    , const std::string& classname
    , std::function<void(::google::protobuf::Message* msg)> cb) {
    std::stringstream ss;
    ss << "/tmp/dps_" << rand() << "_" << rand() << ".proto";
    std::ofstream ofs(ss.str());
    ofs << proto_string;
    ofs.close();
    return DynamicParseFromPBFile(ss.str(), classname, cb);
}

測試代碼

#include <google/protobuf/message.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/compiler/importer.h>

#include "sylar.pb.h"

#include <functional>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

//filename proto文件名
//classname proto文件裏面的class, 格式package.Classname
//cb, 創建好proto文件裏面的classname類之後執行的回調,可以在該回調裏面操作數據
int DynamicParseFromPBFile(const std::string& filename
    , const std::string& classname
    , std::function<void(::google::protobuf::Message* msg)> cb);

//proto_string proto文件的內容
//classname proto文件裏面的class, 格式package.Classname
//cb, 創建好proto文件裏面的classname類之後執行的回調,可以在該回調裏面操作數據
int DynamicParseFromPBString(const std::string& proto_string
    , const std::string& classname
    , std::function<void(::google::protobuf::Message* msg)> cb);

int main(int argc, char** argv) {
	//創建一個測試類,並進行初始化
    sylar::Test test;
    test.set_name("test_name");
    test.set_age(100);
    test.add_phones("138xxxxxx");
    test.add_phones("139xxxxxx");

    for(int i = 0; i < 3; ++i) {
        auto* a = test.add_aa();
        a->set_name("a_name_" + std::to_string(i));
        a->set_age(100 + i);
    }
    std::string pb_str;
	//把測試類,序列化到string中,以供後續解析
    test.SerializeToString(&pb_str);
	//打印測試類的數據信息
    std::cout << test.DebugString() << std::endl;
    std::cout << "===============================" << std::endl;
	//從文件中sylar.proto中的sylar.XX類,解析上面測試類的序列化二進制
	//並輸出序列化後的sylar.XX的信息
    DynamicParseFromPBFile("sylar.proto", "sylar.XX"
        , [pb_str](::google::protobuf::Message* msg) {
        if(msg->ParseFromString(pb_str)) {
            std::cout << msg->DebugString() << std::endl;
        }
    });
    std::cout << "===============================" << std::endl;
	//從文件中sylar.proto中的sylar.Test,解析上面測試類的序列化二進制
	//並輸出序列化後的sylar.Test的信息
    DynamicParseFromPBFile("sylar.proto", "sylar.Test"
        , [pb_str](::google::protobuf::Message* msg) {
        if(msg->ParseFromString(pb_str)) {
            std::cout << msg->DebugString() << std::endl;
        }
    });

	//字符串pb文件內容
    std::string pbstr = "package xx;\n"
        "message BB { \n"
        "    optional string name = 1; \n"
        "    optional int32 age = 2; \n"
        "} \n"
        "message TT { \n"
        "    optional string name = 1; \n"
        "    optional int32 age = 2; \n"
        "    repeated string phones = 3; \n"
        "    repeated BB aa = 4; \n"
        "}";

    std::cout << "===============================" << std::endl;
	//pbstr的proto信息中的xx.TT,解析上面測試類的序列化二進制
	//並輸出序列化後的xx.TT的信息
    DynamicParseFromPBString(pbstr, "xx.TT"
        , [pb_str](::google::protobuf::Message* msg) {
        if(msg->ParseFromString(pb_str)) {
            std::cout << msg->DebugString() << std::endl;
        }
    });

    return 0;
}

測試結果輸出

name: "test_name"
age: 100
phones: "138xxxxxx"
phones: "139xxxxxx"
aa {
  name: "a_name_0"
  age: 100
}
aa {
  name: "a_name_1"
  age: 101
}
aa {
  name: "a_name_2"
  age: 102
}

===============================
name: "test_name"
aa {
  name: "a_name_0"
  age: 100
}
aa {
  name: "a_name_1"
  age: 101
}
aa {
  name: "a_name_2"
  age: 102
}
2: 100
3 {
  6: 0x7878787878783833
}
3 {
  6: 0x7878787878783933
}

===============================
name: "test_name"
age: 100
phones: "138xxxxxx"
phones: "139xxxxxx"
aa {
  name: "a_name_0"
  age: 100
}
aa {
  name: "a_name_1"
  age: 101
}
aa {
  name: "a_name_2"
  age: 102
}

===============================
name: "test_name"
age: 100
phones: "138xxxxxx"
phones: "139xxxxxx"
aa {
  name: "a_name_0"
  age: 100
}
aa {
  name: "a_name_1"
  age: 101
}
aa {
  name: "a_name_2"
  age: 102
}

其他相關

C++ 實用技術 – GOOGLE PROTOBUF反射技術 – 基礎API
C++ 實用技術 – GOOGLE PROTOBUF反射技術 – 轉JSON格式
C++ 實用技術 – GOOGLE PROTOBUF反射技術 – 轉成YAML格式

個人主頁

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章