protobuf 的交叉编译使用(C++)
前言
为了提高通信效率,可以采用 protobuf 替代 XML 和 Json 数据交互格式,protobuf 相对来说数据量小,在进程间通信或者设备之间通信能够提高通信速率。下面介绍 protobuf 在 ARM 平台上的使用。
简介
官方文档给出的定义和描述:
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。
简单来说:
和平台无关,和开发语言无关(支持多种语言和多个平台)
高效:数据量小,因此更快
扩展性和兼容性较好
使用方式
编译安装
1、以下仅介绍在嵌入式设备软件中使用,即交叉编译开发环境,以 protobuf-3.19.3为例
$ tar -xzvf protobuf-cpp-3.19.3.tar.gz
$ cd protobuf-3.19.3/
$ ./autogen.sh
2、配置交叉编译环境和编译安装后的路径,并编译
$ ./configure --host=arm-linux CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ --prefix=/home/protobuf/linux
$ make -j;make install
3、此时会在 /home/protobuf/linux 生成三个文件夹
bin:可执行文件 protoc,可以将对应的 proto 文件生成代码的工具(在 ARM 下使用,所以这个不用)
lib:对应的库,包含静态库和动态库等。还有对应裁剪版功能的lite库
include:集成时需要包含的头文件
4、重新配置编译环境和安装后的路径,并编译(Ubuntu系统)
$ ./configure --prefix=/home/protobuf/ubuntu
$ make -j;make install
5、此时会在 /home/protobuf/ubuntu 生成三个文件夹,和上述一样(Ubuntu 的编译环境),但是这个我们只需要bin文件即可(我们使用的开发环境是 Ubuntu,所以通常都是在 Ubuntu 上执行protoc,用来生成对应的代码)
使用步骤
1、创建 .proto 文件,定义数据结构,如:
// 指定版本 (默认)使用protobuf2
syntax = "proto2";
// 指定命名空间
package ExampleNC;
// 例1: 在 xxx.proto 文件中定义 CExample message
message CExample
{
optional string stringDesc = 1;
optional bytes bytesVal = 2;
}
2、通过可执行文件 protoc 将 .proto 文件生成对应的代码文件
$ protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto
$SRC_DIR是.proto文件所在的路径,$DST_DIR是生成代码的路径,--cpp_out 是表示生成 C++代码;
生成对应的 xxx.pb.h 和 xxx.pb.cc 两个文件,可将这两个文件放入需要集成的代码中(包括交叉编译生成的头文件include)
3、通过生成的类 CExample 定义变量,设置对应的值,如:
CExample *pInfo = new CExample();
pInfo->set_stringdesc("test"); // 赋值
printf("info: %s\n", pInfo->DebugString().c_str());// 打印设置的值(文本格式,lite版本不支持)
int length = pInfo->ByteSize();
char *pBuf = (char *)malloc(length);
pInfo->SerializeToArray(pBuf, length);// 序列化为hex
printf_hexdump("HEX:", pBuf, length);// 打印序列化后的hex数据
CExample *pOutInfo = new CExample();
pOutInfo->ParseFromArray(pBuf, length);//反序列化
printf("OUTinfo: %s\n", pOutInfo->DebugString().c_str());// 打印设置的值(文本格式,lite版本不支持)
free(pBuf);
其中,定义新的类后,若没有进行赋值操作的变量,序列化中则没有该数据内容
常见问题
在代码运行时,出现以下错误
[libprotobuf ERROR google/protobuf/descriptor_database.cc:641] File already exists in database: ais_msg.proto
[libprotobuf FATAL google/protobuf/descriptor.cc:2021] CHECK failed: GeneratedDatabase()->Add(encoded_file_descriptor, size):
terminate called after throwing an instance of 'google::protobuf::FatalException'
what(): CHECK failed: GeneratedDatabase()->Add(encoded_file_descriptor, size):
网上提供的解决办法
【转自 https://m.newsmth.net/article/Programming/single/5862/3】
Protobuf是Google的一个开源项目,它的大部分代码是用C++写的。当别的程序想要使用protobuf时,既可以采用动态链接,也可以采用静态链接。Google内部主要是采用静态链接为主。而在Linux的世界里,大部分发行版都把Protobuf编译成了动态库。
最佳实践
如果你的Project本身是一个动态库,那么你应该避免在它的公开接口中用到任何protobuf的符号,并且采用静态链接到protobuf的方式。同时你应该在dllmain中调用google::protobuf::ShutdownProtobufLibrary()来清理protobuf使用过内存。
如果你的Project本身是一个静态库,那么决定权不在你手里,而且最终把你的静态库编译成PE/ELF文件的那个人手里。但是你需要在你的build system中留出接口让他可以告知你这个信息。
如果你的Project本身是一个动态库,并且你公开接口中用到了protobuf的符号,那么你必须动态链接到protobuf。 否则当你跨DLL传送protobuf的对象时,如果这个对象在A.DLL中创建,但是在B.DLL中被销毁,那么就会导致程序崩溃。因为当你采用静态链接到Protobuf时,每个DLL内部都有一个protobuf的副本,并且protobuf内部有自己的内存池。跨DLL传输对象就会导致该对象可能在不属于自己的内存池中被释放。
动态链接的注意事项
首先,不推荐在Windows上这么做。 因为protobuf本身是基于C++的,而Windows上DLL的导出符号应该都是C风格的,不应含有任何STL、std::string这样的东西。 如果你一定要这么做,那么你就会收到C4251警告。这是一个level 1的警告,属于最高严重等级。
如果你决定动态链接到protobuf,并且目标平台是Windows操作系统,那么你应该在编译你的project的源代码的时候"#define PROTOBUF_USE_DLLS"。 这样链接器才知道应该使用dllimport的方式去寻找protobuf的符号。 Linux不需要这么做。但是Linux需要注意把code编译成PIC的。 同时,在Windows上需要注意所有代码必须采用动态链接到CRT,而不能采用静态链接。 这条适用于libprotobuf.dll自身以及它的所有使用者。
无论是Windows还是Linux,动态链接带来的另一个问题是:从.proto生成的那些C/C++代码可能也需要被编译成动态库共享。因为protobuf本身有一个global的registry。每个message type都需要去那里注册一下,而且不能重复注册。所以,假如你在A.DLL中定义了某些message type,那么B.DLL就只能从A.DLL的exported的DLL interface中使用这些message type, 而不能从proto文件中重新生成C/C++代码并包含到B.DLL里去。并且B.DLL也不能私自的去修改、扩展这个message type。据说换成protobuf-lite就能避免这个问题,但是Google官方并没有对此表态。
另外,protobuf动态库自身不能被unload然后reload。 这个限制让我很意外,但是Google自己说他们在设计的时候从来没考虑过这样的使用场景。不过,在Linux上这其实是很常见的事情,GLIB自身都不支持unload。
糟糕的用例:Tensorflow
首先,tensorflow作为一个python的plugin,它必须是动态库,不能是静态库。
Tensorflow选择了静态链接到protobuf。
Tensorflow想要支持动态加载plugin。每个plugin是一个动态库。
plugin本身需要访问Tensorflow的接口,而这些接口常常又含有protobuf的符号。Tensorflow会暴露(provide) libprotobuf 的部分符号。如果这个plugin需要的符号恰好在tensorflow中都能找到,那么很好。 但事情并非总是如此, 因为Tensorflow它只有一个partial的libprotobuf,它只包含它自己所必须的那部分protobuf的code。当这个plugin想要的超出了tensorflow所能提供的范畴,写plugin的人就会尝试把protobuf加到link command中。这样就会变得非常非常危险,程序随时会崩溃。因为它会在两个不同的protobuf副本之间传送protobuf的对象。 所以,不要看到“unresolved external symbol”就不动脑子的把缺的库加上,有时候这个错误代表的是更深层次的问题。
糟糕的用例: cmake
cmake 3.16做了一个火上浇油的事情:当你使用find_package(Protobuf)的时候,你需要提前知道你找到的究竟是动态库还是静态库,如果是静态库那么你需要设置Protobuf_USE_STATIC_LIBS成OFF,否则在Windows上链接会失败。请注意: 不是cmake告诉你它找到的是什么,而是你要主动告诉它,它找到的会是什么。
首先说明错误的具体原因:因为需要通信多个进程模块都集成了相同的 *.pb.cc 和 *.pb.h 文件进行编译(动态库或者执行文件),且在编译时通过动态库 libprotobuf.so 的方式进行链接。
因为这个,导致在运行时报错,通过网上查找得到:protobuf 本身有一个 global 的 registry。每个 message type 都需要去那里注册一下,而且不能重复注册。上述的
Add错误就是因为注册失败,原因就是因为这几个中重复注册了(多份*.pb.cc实现)。
解决方案
根据网上的解决方案,我自己也实验了,归纳如下:
1、静态库编译,使用 libprotobuf.a,即多个编译目标通过静态库的方式链接,但是这种方式势必会导致程序编译后的目标大小增大,不太适合 ARM 容量小的设备。如果编译目标是动态库,则需要在交叉编译 protobuf 加上-fPIC。
$ ./configure --host=arm-linux --disable-shared CFLAGS="-fPIC -fvisibility=hidden" CXXFLAGS="-fPIC -fvisibility=hidden" CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ --prefix=/home/protobuf/linux
$ make -j;make install
或者改 configure 文件再执行 configure
// 改成下面的样子(不同版本位置不对,所以可以搜索 ac_cv_env_CFLAGS_set)
if test "x${ac_cv_env_CFLAGS_set}" = "x"; then
CFLAGS="-fPIC"
fi
if test "x${ac_cv_env_CXXFLAGS_set}" = "x"; then
CXXFLAGS="-fPIC"
fi
$ ./configure --host=arm-linux --disable-shared CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ --prefix=/home/protobuf/linux
$ make -j;make install
2、使用 protobuf-lite 版本,可以通过动态库 libprotobuf-lite.so 链接,但这个版本裁剪了很多功能,只保留了基本功能,这个需要修改 *.proto 文件,增加 option optimize_for 选项,重新生成 *.pb.cc 和 *.pb.h 文件,然后各模块集成编译即可。
// 指定版本 (默认)使用protobuf2
syntax = "proto2";
// 使用 lite 版本
option optimize_for = LITE_RUNTIME;
// 指定命名空间
package ExampleNC;
// 例1: 在 xxx.proto 文件中定义 CExample message
message CExample
{
optional string stringDesc = 1;
optional bytes bytesVal = 2;
}
3、将生成的 *.pb.cc 和 *.pb.h 和 libprotobuf.a 编译成一个新的动态库 libprotobuf-new.so,其他模块包含 *.pb.h 文件并通过动态库的方式链接这个新的动态库即可。
这种方式解决了上述问题,也保留了全部功能,但是对于后续的维护存在一定问题,如果更新 *.proto 文件,重新生成并编译了 libprotobuf-new.so,那么其他使用的模块也必须都更新*.pb.h 文件重新编译,否则在使用中会遇到:
- 程序包只替换了 libprotobuf-new.so ,程序运行时可能存在踩踏数据的情况,引发系统异常
- 程序包只替换了其他模块的库,并没有替换 libprotobuf-new.so ,就会导致进程崩了
所以,在多人合作开发的程序实现数据通信时,这种方式并不好,兼容性太差,因为需要替换所有涉及的模块动态库或者执行文件
以上三种解决方案各有利弊,可以根据实际情况选择
protobuf 的交叉编译使用(C++)的更多相关文章
- protobuf/android 交叉编译笔记
protobuf 交叉编译笔记 目标是使用 android ndk 的工具链编译出 android armeabi-v7a 可用的 protobuf 库. 交叉编译环境配置 windows 平台 下载 ...
- edgex0.7.1_1.0.1的X86编译和交叉编译
一. X86编译 1. 安装zeromq库 根据setup script安装: wget https://github.com/zeromq/libzmq/releases/download/v4.2 ...
- Tensorflow ARM交叉编译错误集锦
版权声明:本文为博主(Jimchen)原创文章,未经博主允许不得转载. ttps://www.cnblogs.com/jimchen1218/p/11611975.html 前言: Tensorflo ...
- python通过protobuf实现rpc
由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...
- Linux主机上使用交叉编译移植u-boot到树莓派
0环境 Linux主机OS:Ubuntu14.04 64位,运行在wmware workstation 10虚拟机 树莓派版本:raspberry pi 2 B型. 树莓派OS: Debian Jes ...
- Ubuntu 16.04 安装 arm-linux-gcc 嵌入式交叉编译环境 问题汇总
闲扯: 实习了将近半年一直在做硬件以及底层的驱动,最近要找工作了发现了对linux普遍要求很高,而且工作岗位也非常多,所以最近一些时间在时不时地接触linux. 正文:(我一时兴起开始写博客,准备不充 ...
- Protobuf使用规范分享
一.Protobuf 的优点 Protobuf 有如 XML,不过它更小.更快.也更简单.它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍.你可以定义自己的数据结构 ...
- Linux 14.04lts 环境下搭建交叉编译环境arm-linux-gcc-4.5.1
交叉编译工具链是为了编译.链接.处理和调试跨平台体系结构的程序代码,在该环境下编译出嵌入式Linux系统所需要的操作系统.应用程序等,然后再上传到目标板上. 首 先要明确gcc 和arm-linux- ...
- java netty socket库和自定义C#socket库利用protobuf进行通信完整实例
之前的文章讲述了socket通信的一些基本知识,已经本人自定义的C#版本的socket.和java netty 库的二次封装,但是没有真正的发表测试用例. 本文只是为了讲解利用protobuf 进行C ...
随机推荐
- Halo 开源项目学习(四):发布文章与页面
基本介绍 博客最基本的功能就是让作者能够自由发布自己的文章,分享自己观点,记录学习的过程.Halo 为用户提供了发布文章和展示自定义页面的功能,下面我们分析一下这些功能的实现过程. 管理员发布文章 H ...
- Spring mvc 使用@RequestBody 500错误
今天在使用@RequestBody的时候,遇到一个http500错误,记录一下 让我们来看看我是怎么样错的,贴上代码 @PostMapping("/User") public Us ...
- 代码源 每日一题 分割 洛谷 P6033合并果子
题目链接:切割 - 题目 - Daimayuan Online Judge 数据加强版链接: [NOIP2004 提高组] 合并果子 加强版 - 洛谷 题目描述 有一个长度为 ∑ai 的木板,需要 ...
- 操作系统深度研究(75页PPT)
上一篇:命令行版的斗地主你玩过没? 内容覆盖操作系统基本概念.分类.关键技术,体系架构,发展历程和主流国产操作系统厂商分析. 文中报告节选自兴业证券经济与金融研究院已公开发布研究报告,具体报告内容及相 ...
- 协议层安全相关《http请求走私与CTF利用》
0x00 前言 最近刷题的时候多次遇到HTTP请求走私相关的题目,但之前都没怎么接触到相关的知识点,只是在GKCTF2021--hackme中使用到了 CVE-2019-20372(Nginx< ...
- Linux版本的项目环境搭建
项目环境docker及docker-compose文档 1.Linux环境介绍 centos7.6 16G以上内存空间(至少8G) 2.静态IP设置 1.找到配置文件 cd /etc/sysconfi ...
- 一个 curl 配置引发的惨案
问题 这两天想装新版本的 node,发现 nvm 一直报下面这个错误.我反复 Google 了,但是并没有找到一条我能用的. 痛苦 我起初一直怀疑是我用的 zsh-nvm 抽疯,所以今天有空就把它还有 ...
- MySQL数据库3
内容概要 自增特性 约束条件之外键 外键简介 外键关系 外键SQL语句之一对多关系 外键SQL语句之多对多关系 外键SQL语句之一对一关系 查询关键字 数据准备 查询关键字之select与from 查 ...
- python和numpy中sum()函数的异同
转载:https://blog.csdn.net/amuchena/article/details/89060798和https://www.runoob.com/python/python-func ...
- (持续更新)虚树,KD-Tree,长链剖分,后缀数组,后缀自动机
真的就是讲课两天,吸收一个月呢! \(1.\)虚树 \(2.\)KD-Tree \(3.\)长链剖分 \(4.\)后缀数组 后缀数组 \(5.\)后缀自动机 后缀自动机