序列化协议Protobuf(一):MSVC环境编译
C++ Protocol Buffers
protobuf
是google推出的一个数据序列化/反序列化库,和Json/XML
类似,但是其以二进制编码进行传输,而Json/XML
以文本格式传输,因此文件大小更小(3-10倍)、传输速度更快(20-100倍);
protobuf
是一种语言无关的库,支持C++、Python、Java、Js、Ruby等主流语言,广泛用于网络、数据传输中,例如网络协议grpc;
但protobuf
也不是完美的,例如它不能像Json一样即插即用,对C++而言尤为复杂,需要编译器以及runtime环境,对其他语言略微友好一点;以下记录了Win10
+
MSVC编译protobuf
过程,尽量列出要点,对版本无要求的可以按照本文进行,经过二次验证成功率较高,对于非C++用户/配置/VS小白/高血压患者建议使用Unix环境包管理器或vcpkg等安装依赖,无需参考本文(本文也针对禁git
的生产环境。
protobuf
的github仓库给出了各种语言的安装方法,但就C++安装而言其指向仍然是不清晰的,这也是issue和迭代频繁的原因,网上提供的方法许多已经过时,因为protobuf
不再根据语言划分出cpp包,而且3.21以后版本增加了Abseil的依赖,涉及版本匹配问题、C++标准等,官方对此也没有加以具体规范说明,这也是官方和许多博客建议使用包管理器的原因;
对Cpp而言问题更是如此,因为作者在win64/32安装包中提到:This binary is intended for users who want to use Protocol Buffers in languages other than C++ but do not want to compile protoc themselves.
,无依赖情况下C++甚至无法使用该二进制程序生成的头文件,也无法进行序列化读写,因为其需要相关的环境支持,因此接下来只能通过编译获取了。
环境编译
Win10 + Visual Studio 2019(MSVC 16)
CMake 3.27.3
Abseil 20240722版本
protobuf 29.3
CMake编译abseil
abseil
是C++标准库的补充,protobuf
的编译依赖于这个静态库,需要使用Cmake编译这个静态库:从此处下载。
注意第一个要点,较新的protobuf使用的abseil
宜使用C++ 17标准
编译,在CMake环境中点击Add Entry
添加CMAKE_CXX_STANDARD
,string
填入17
;
建议使用cmake的gui窗口,新建build文件夹,选择输出文件夹为build,直接configure并且generate即可,可见build出现MSVC的解决方案absl.sln
文件,使用VS打开,默认采用Debug模式,将标准改为17(我也不确定二者谁真正起作用),使用ctrl+F5
执行生成任务,出现“"成功92个、失败为0个”,说明静态库生成没有出错;
进入build文件夹指定输出路径并打包:
1 | cmake --install . --prefix D:\Documents\Desktop\abseil\abseil-cpp-20240722.0\output --config Debug |
output
文件夹下出现lib
和include
,编译打包完成。
CMake编译protobuf
从github获取realease,我选择的版本目前是25年的最新版本protobuf-29.3.zip
,不要使用win32/64等;
同样方式建立build文件夹,并使用CMake gui窗口指定源文件夹与输出文件夹,此处:
取消勾选
protobuf_BUILD_TESTS
,因为这里我没有clone相关的测试文件;勾选
protobuf_BUILD_SHARED_LIBS
,这里选择输出动态库,想要静态库的无需勾选;
第一次configure,会发现一堆红色,实际上报错的只有一个,指向:D:\Documents\Desktop\proto\protobuf-29.3\build\third_party\abseil-cpp
缺少CMakeLists.txt
:将刚刚abseil
含CMakeLists.txt
(即根目录)拷贝到该文件夹,重新configure,配置无误,点击generate生成sln并通过VS打开;
找到左侧CMakePredefinedTargets
下的ALL_BUILD
,将其属性标准设置成C++ 17
,然后右键点击生成,出现"0错误",动态库编译完成;
此时的依赖分散在若干个文件夹,如protobuf-29.3\build\bin\Debug
存储了S可执行文件和dll、lib库文件S,它们是我们需要的动态库依赖;protobuf-29.3\src\google\protobuf
存储的是头文件依赖,通过此条命令好好打包一下:
在build中执行: 1
cmake --install . --prefix D:\Documents\Desktop\proto\protobuf-29.3\build\output --config Debug
output
下出现了bin、include、lib
三个文件夹,其中:
bin:存储了4个可执行文件,最主要是
protoc.exe
以及其动态库依赖;include:存储了头文件依赖,包含
google、absl、upb
等等;lib:存储了
cmake、pkgconfig
两个文件夹以及若干.lib
库文件;
注:我使用27.5编译时,打包没有bin文件,lib下也没有库文件,但文件夹分散是存在的,最后编译没有成功,没有验证是版本特性还是说明出现问题;
至此,得到上述文件,protobuf
编译成功。将bin文件夹包含到环境变量中,验证:
1
protoc --version
protobuf验证
工程目录下新建一个data.proto: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26syntax = "proto3";
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
message Address {
string country = 1;
string detail = 2;
}
message Person {
int32 id =1;
string name = 2;
int32 age = 3;
repeated string email = 4;
repeated PhoneNumber phone = 5;
Address address = 6;
}
使用命令将其转换成头文件data.pb.h和源文件data.pb.cc接口:
1
protoc .\data.proto --cpp_out=.\
protobuf
选择的是动态库,一定要在data.pb.h
下定义#define PROTOBUF_USE_DLLS
,否则默认使用静态库会出现错误;
在工程中添加头文件、库文件依赖关系,主要是三个地方:
项目 - 属性 - VC++目录 - 包含目录或外部包含目录添加刚刚install的路径,如
D:\Documents\Desktop\proto\protobuf-29.3\build\output\include
上述同样的地方加入库目录:
D:\Documents\Desktop\proto\protobuf-29.3\build\output\lib
如果打包并不规范,可能目录路径有别;
最后添加依赖项,此处有若干方法:
法1: 项目 - 属性 - 链接器 - 附加依赖项中添加:
法2: 左侧资源管理器 - 工程右击 - 添加 - 新建筛选器 - 重命名成库文件并且添加文件夹的lib文件
添加对象是libprotocd.lib
、libprotobufd.lib
、abseil_dll.lib
,有的只需要添加前二者,我这里三者均需要;
一些说明:
第一次建议将所有lib通过法二先行导入,以排除符号缺失故障;
在使用27.5 Realease版本protobuf时生成命名是
libprotoc.lib
和libprotobuf.lib
,缺少d,未验证失败是否与此有关,也许只是版本特性影响,这里成功版本均采用29.3的Debug以及Aseil的Debug版本,且为C++ 17标准;
最后验证正常,这里甚至无需写入什么复杂逻辑代码,空实现一个main函数且将data.pb.h
包含在cpp文件中,如果头文件被成功包含且能通过编译,即可正常使用;注意检查是否x64平台,因为所有的库均在x64下生成;
Q&A
- 编译
abseil/protobuf
,出现visual studio2019无法启动程序,\ALL_BUILD 拒绝访问
:
- 第一次生成完属正常现象,无需理会,只需要看到"失败为0个",静态库/动态库成功生成即可;
- 4个无法解析外部符号:内容如下:
1
2
3
4无法解析的外部符号 "protected: static struct google::protobuf::internal::DescriptorMethods const google::protobuf::Message::kDescriptorMethods" (?kDescriptorMethods@Message@protobuf@google@@1UDescriptorMethods@internal@23@B)
无法解析的外部符号 "class std::array<char,7> const absl::lts_20240722::log_internal::kCharNull" (?kCharNull@log_internal@lts_20240722@absl@@3V?
无法解析的外部符号 "private: static struct google::protobuf::internal::ThreadSafeArena::ThreadCache google::protobuf::internal::ThreadSafeArena::thread_cache_" (?thread_cache_@ThreadSafeArena@internal@protobuf@google@@0UThreadCache@1234@A)
无法解析的外部符号 "class google::protobuf::internal::ExplicitlyConstructed<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,8>
data.pb.h
头文件没有添加#define PROTOBUF_USE_DLLS
,导致无法解析部分符号;
- 出现4个以上乃至几十个不等未解析外部符号:
- 多因素影响,总而言之是库文件没有正确配置,根据具体符号排查原因,例如protobuf动态库或Abseil静态库未正确引入,或者C++标准未严格使用C++ 17等、使用x64编译但main使用了默认x86编译;
- 编译成功但执行出现“无法定位入口....dll”
- 这里是我配置了环境变量后仍然出现的报错,不清楚为什么没有从环境变量找到引入的三个动态库,主要没有找到abseil_dll.dll,最便捷的方式将其复制到程序同级目录即可。