protobuf自动填充字段数据
Protobuf自动填充字段数据
〇、需求
需要从一个结构中,比如map或variant,把数据填充到protobuf的各个字段中。
源数据:map
目的数据:protobuf
注意:只有protobuf有的字段才能进行填充,variant中有的值,但是protobuf中没有的字段,是填充不了的。
一、解决方案
生成protobuf文件时,打开使用反射机制的开关,然后遍历map或variant,利用protobuf的反射机制进行字段的填充。
二、详细步骤
2.1生成protobuf
反射是默认启用的,不需要特定的编译开关,去打开编译,避免使用–cpp_out=optimized这样的编译选项,可能会关闭掉反射。
protobuf_24_3\bin\windows\release\protoc.exe -I="protobuf_24_3\include" -I=../ -I=./ --cpp_out=dllexport_decl=MY_EXPORT:../out_dir/ --experimental_allow_proto3_optional ./myProto.proto
-
protobuf_24_3\bin\windows\release\protoc.exe
:- 这是 Protocol Buffers 编译器的可执行文件路径,用于将
.proto
文件编译成 C++ 源代码。
- 这是 Protocol Buffers 编译器的可执行文件路径,用于将
-
-I="protobuf_24_3\include"
:-I
选项用于指定包含目录。这告诉编译器在编译过程中查找.proto
文件时还需要在protobuf_24_3\include
目录中查找。
-
-I=../
和-I=./
:- 这两个也是包含目录的指定。
../
表示上一级目录,而./
表示当前目录。这样.proto
文件或依赖的其他.proto
文件可以在这些目录中被找到。
- 这两个也是包含目录的指定。
-
--cpp_out=dllexport_decl=MY_EXPORT:../out_dir/
:-
--cpp_out
选项指定输出 C++ 代码的目录和特定选项:
dllexport_decl=MY_EXPORT
是编译选项,它指定生成的 C++ 代码在使用时定义了一个宏MY_EXPORT
。这个宏通常用于控制 DLL 中的符号导出,这对于 Windows 动态链接库(DLL)是很重要的。../out_dir/
是生成的 C++ 文件将被输出到的目录路径(相对于当前工作目录)。
-
-
--experimental_allow_proto3_optional
:- 这个标志允许使用 Protocol Buffers 3 中的可选字段特性(optional fields)模型。这意味着可以在
.proto
文件中定义字段为可选,使用这个选项可以让你的代码兼容这一特性。
- 这个标志允许使用 Protocol Buffers 3 中的可选字段特性(optional fields)模型。这意味着可以在
-
./myProto.proto
:- 这是输入的
.proto
文件,编译器将对其进行处理并生成相应的 C++ 代码。
- 这是输入的
2.2 开发功能
主要提供三种方法,dllLoad,getMessage,Map2PB.
- dllLoad,负责对protobuf共享库的动态加载
- getMessage,通过protobuf的message名字获取对应的message对象
- ConvertTo,负责map或者Variant中数据到protobuf字段数据内容的填充
2.2.1 dllLoad函数
bool dllLoad(IN const std::string& dllPath){
boost::dll::shared_library dllHelper;
try{
dllHelper.load(dllPath);
}catch(const std::exception& e){
return false;
}
if(!dllHelper.is_loaded()){
return false;
}
return true;
}
2.2.2 getMessage函数
::google::protobuf::DynamicMessageFactory g_MessageFactory;
::google::protobuf::Message* getMessage(const std::string& messageName){
std::string prefix = "your own protobuf prefix";
const ::google::protobuf::DescriptorPool* pool = ::google::protobuf::DescriptorPool::generated_pool();
auto descriptor = pool->FindMessageTypeByName(prefix+messageName);
if(!descriptor){
return nullptr;
}
return g_MessageFactory.GetPrototype(descriptor)->New();
}
2.2.3 ConvertTo函数
每个人自定义的variant可能不一样,我的variant定义如下
message Dict{
map<string,Variant> list=1;
}
message Variant{
oneof oneof_variant{
//basic
bool boolvalue = 1;
int32 int32value=2;
...
//struct
Dict dictvalue=3;
}
}
variant可以是一个map,这个map存的键值对也是string,variant。
bool ConvertTo(IN Variant& in, OUT ::google::protobuf::Message* out){
auto& map = in.dictvalue().list();
for(auto iter :map){
auto fieldname = iter.first;
auto& fieldvalue = iter.second;
ret = SetPB(out,fieldname,fieldvalue);
if(ret!= true) return false;
}
return true;
}
其中SetPB函数如下
bool SetPB(OUT ::google::protobuf::Message* rootMsg,IN const std::string& fieldname,IN Variant& var){
auto descriptor = rootMsg->GetDescriptor();
auto reflection = rootMsg->GetReflection();
auto field = descriptor->FindFieldByName(fieldname);
if(nullptr ==field){return false;}
auto toType = field->type();
auto fromType = var.oneof_variant_case();
switch(toType){
case FieldDescriptor::TYPE_DOUBLE:{
ret = SET_DOUBLE(field,reflection,rootMsg,var);
}break;
}
}
各种类型值的转换的就随自定义的variant类型来提供不同类型的设置方法了,这里只展示设置bool类型变量方法。
bool SET_DOUBLE(const ::google::protobuf::fieldDescriptor* field,const ::google::protobuf::Reflection* reflection,::google::protobuf::Message* rootMsg,Variant& var){
if(!field->is_repeated()){
reflection->SetDouble(rootMsg,field,var.doublevalue());
return true;
}
return true;
}
在这一步field可能是数组类型,也可能不是,需要进行判断。根据自己需求提供给protobuf填充值的方法。
2.2.4 总结
最后调用步骤就是,先获取到message的dllpath,然后调用dllLoad加载动态库,然后再利用messagename调用getMessage获取改messagename对应的message对象,最后利用ConvertTo函数,对该message对象进行填充值。