Protobuf是google开发的一个序列化和反序列化的协议库,我们可以自己设计传递数据的格式,通过.proto文件定义我们的要传递的数据格式。例如,在深度学习中常用的ONNX交换模型就是使用.proto编写的。我们可以通过多种前端(MNN、NCNN、TVM的前端)去读取这个.onnx这个模型,但是首先你要安装protobuf。

在之前的博文中已经简单介绍了onnx,其中onnx.proto就代表了onnx模型的基本数据结构。一般来说,protobuf经常搭配Cmake使用,Cmake有官方的modules,可以通过简单的几个命令protobuf_generate_cpp来生成对应的.pb.cc.pb.h

简单的例子:

find_package(Protobuf REQUIRED)
include_directories(${Protobuf_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS EXPORT_MACRO DLL_EXPORT foo.proto)
protobuf_generate_python(PROTO_PY foo.proto)
add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(bar ${Protobuf_LIBRARIES})

但是这个例子太简单了,如果我们的.proto文件只有一个或者说都只在一个目录里,那用这个命令没什么毛病...

但如果是这种情况,我们的文件目录如下:

├── CMakeLists.txt
├── README.md
├── meta
│ └── proto
│ ├── CMakeLists.txt
│ └── common
│ ├── bar
│ │ ├── CMakeLists.txt
│ │ └── bar.proto
│ └── foo
│ ├── CMakeLists.txt
│ └── foo.proto
└── src
├── CMakeLists.txt
├── c_proto.cc
└── c_proto.hh

其中foo.proto文件如下:

message foo_msg
{
optional string name = 1;
}

bar.proto的文件如下:

import "common/foo/foo.proto";

message bar_msg
{
optional foo_msg foo = 1;
optional string name = 2;
}

如上,bar文件引用foo,而且这两个不在一个目录,如果直接使用protobuf_generate_cpp来生成,直接会报错。(这个例子取自Yu的一篇博文

也想过把他俩放到同一个目录...然后bar.proto中import的代码就要修改,虽然这样可以,但显然是不适合大型的项目。

而这个大型项目显然就是mediapipe...折磨了我好久。

关于mediapipe的详细介绍在另一篇文章。mediapipe中使用了大量的ProtoBuf技术来表示图结构,而且mediapipe原生并不是采用cmake来构建项目,而是使用google自家研发的bazel,这个项目构建系统我就不评价了,而现在我需要使用Cmake来对其进行构建。

这也是噩梦的开始,mediapipe的.proto文件很多,核心的framework的目录下存在很多的.proto文件,根目录和子目录都有.proto文件:

而且每个proto文件之间存在引用的顺序,framework根目录下的calculator.proto文件:

// mediapipe/framework/calculator.proto
syntax = "proto3"; package mediapipe; import public "mediapipe/framework/calculator_options.proto"; import "google/protobuf/any.proto";
import "mediapipe/framework/mediapipe_options.proto";
import "mediapipe/framework/packet_factory.proto";
import "mediapipe/framework/packet_generator.proto";
import "mediapipe/framework/status_handler.proto";
import "mediapipe/framework/stream_handler.proto";

每个.proto文件都import了其他目录下的文件,这里的import类似于C++中的include,但是这里的import又可以相互引用,例如上述的status_handler.proto也引用了mediapipe_options.proto

如果直接对上述所有的.proto文件直接使用protobuf_generate_cpp命令,会直接报错,因为这些文件不在一个目录,而且import的相对目录也无法分析。另外,不同目录内的.cc文件会引用相应目录生成的.pb.h文件,我们需要生成的.pb.cc.pb.h在原始的目录中,这样才可以正常引用,要不然需要修改其他源代码的include地址,比较麻烦。

CLion中Cmake来编译proto生成的.pb.cc.pb.h不在原始目录,而是集中在cmake-build-debug(release)中,我们额外需要将其中生成的.pb.cc.pb.h文件移动到原始地址(Clion的情况是这样)。

正确修改cmake

对于这种情况,比较合适的做法是直接使用命令进行生成。

首先找到所有需要编译的.proto文件:

file(GLOB protobuf_files
mediapipe/framework/*.proto
mediapipe/framework/tool/*.proto
mediapipe/framework/deps/*.proto
mediapipe/framework/testdata/*.proto
mediapipe/framework/formats/*.proto
mediapipe/framework/formats/annotation/*.proto
mediapipe/framework/formats/motion/*.proto
mediapipe/framework/formats/object_detection/*.proto
mediapipe/framework/stream_handler/*.proto
mediapipe/util/*.proto
mediapipe/calculators/internal/*.proto
)

接下来,定义相关的目录地址,PROTO_META_BASE_DIR为编译之后生成文件的目录。PROTO_FLAGS很重要,指定编译.proto文件时的总的寻找路径,.proto中的import命令根据根据这个地址去连接其他的.proto文件:

SET(PROTO_META_BASE_DIR ${CMAKE_CURRENT_BINARY_DIR})
LIST(APPEND PROTO_FLAGS -I${CMAKE_CURRENT_SOURCE_DIR})

设置好之后,通过FOREACH去循环之前的.proto文件,依次编译每个文件,然后将生成的.pb.cc.pb.h移动回原始的目录,至此就可以正常工作了。

FOREACH(FIL ${protobuf_files})

    GET_FILENAME_COMPONENT(FIL_WE ${FIL} NAME_WE)

    string(REGEX REPLACE ".+/(.+)\\..*" "\\1" FILE_NAME ${FIL})
string(REGEX REPLACE "(.+)\\${FILE_NAME}.*" "\\1" FILE_PATH ${FIL}) string(REGEX MATCH "(/mediapipe/framework.*|/mediapipe/util.*|/mediapipe/calculators/internal/)" OUT_PATH ${FILE_PATH}) set(PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}${OUT_PATH}${FIL_WE}.pb.cc")
set(PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}${OUT_PATH}${FIL_WE}.pb.h") EXECUTE_PROCESS(
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ${PROTO_FLAGS} --cpp_out=${PROTO_META_BASE_DIR} ${FIL}
)
message("Copying " ${PROTO_SRCS} " to " ${FILE_PATH}) file(COPY ${PROTO_SRCS} DESTINATION ${FILE_PATH})
file(COPY ${PROTO_HDRS} DESTINATION ${FILE_PATH}) ENDFOREACH()

参考链接

http://blog.argcv.com/articles/3884.c

https://www.v2ex.com/t/602363

https://stackoverflow.com/questions/29720410/no-member-found-when-use-cmake-construct-proto/29817843#answer-29727925

撩我吧

  • 如果你与我志同道合于此,老潘很愿意与你交流;
  • 如果你喜欢老潘的内容,欢迎关注和支持。
  • 如果你喜欢我的文章,希望点赞 收藏 评论 三连一下~

想知道老潘是如何学习踩坑的,想与我交流问题~请关注公众号「oldpan博客」。

老潘也会整理一些自己的私藏,希望能帮助到大家,点击神秘传送门获取。

Protobuf在Cmake中的正确使用的更多相关文章

  1. CMake中添加Qt模块的合理方法

    https://www.jianshu.com/p/7eeb6f79a275 转载自这里 用CMake来组织的工程中要用Qt首先要设置.找到Qt相关模块.主要是通过find_package这个CMak ...

  2. 《Dotnet9》系列-Google ProtoBuf在C#中的简单应用

    时间如流水,只能流去不流回! 点赞再看,养成习惯,这是您给我创作的动力! 本文 Dotnet9 https://dotnet9.com 已收录,站长乐于分享dotnet相关技术,比如Winform.W ...

  3. InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式

    InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式 https://mp.weixin.qq.com/s/HGa_90XvC22anabiBF8AbQ 在这篇文章里,我将讨论在MySQL 5 ...

  4. PHP 中如何正确统计中文字数

    PHP 中如何正确统计中文字数?这个是困扰我很久的问题,PHP 中有很多函数可以计算字符串的长度,比如下面的例子,分别使用了 strlen,mb_strlen,mb_strwidth 这个三个函数去测 ...

  5. cmake中的变量和命令的大小写

    1 cmake中要特别注意命令和变量的大小写 2 cmake的内置命令是不区分大小写的 3 cmake内置变量是区分大小写的,或者干脆就说,cmake的所有变量都是区分大小写的 这就是变量和命令的不同 ...

  6. windows系统里Cygwin中如何正确安装wget(图文详解)

    具体步骤,见如下: https://ftp.gnu.org/gnu/wget/ 解压到Cygwin的主目录中,一般是   你的Cygwin目录/home/当前用户名/ . 我这是如下     先./c ...

  7. jquery-easyui的datagrid在checkbox多选时,行选中不正确应,去除高亮的解决方法

    jquery-easyui的datagrid在checkbox多选时,行选中不正确应,去除高亮的解决方法 工作中用到一个具有多选功能的easyui-datagrid在处理cell的点击事件时,不同 ...

  8. 十七.protobuf在rpc中的使用

    关于protobuf在rpc中的使用,设计到gRPC,相关内容待续....

  9. CMake中的两种变量(Variable types in CMake)

    在CMake中存在两种变量:normal variables and cache varialbes .正常变量就像是脚本内部变量,相当于程序设计中定义的局部变量那样.而CMakeLists.txt相 ...

随机推荐

  1. Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本

    Java容器--2021面试题系列教程(附答案解析)--大白话解读--JavaPub版本 前言 序言 再高大上的框架,也需要扎实的基础才能玩转,高频面试问题更是基础中的高频实战要点. 适合阅读人群 J ...

  2. 在kubernetes集群里集成Apollo配置中心(5)之dubbo服务消费者连接apollo实战

    1.在Apollo的portal创建dubbo消费者项目 (1)添加dubbo消费者项目 (2)在dubbo消费者项目中添加配置项 (3)发布 2.通过jenkins构建dubbo消费者镜像 3.登录 ...

  3. linux无需root挂载iso镜像文件

    引言 起初,我在针对deepin制作一款appimage安装工具,想要其实现的功能就是自动获取图标,只需要输入软件名称和分类即可,当然以后也会寻找方案省去手动输入的麻烦. 后来我发现一个有趣的问题 o ...

  4. select用法&原理详解(源码剖析)(转)

    今天遇到了在select()前后fd_set的变化问题,查了好久终于找到一个有用的帖子了,很赞,很详细!!原文链接如下: select用法&原理详解(源码剖析) 我的问题是: 如下图示:在se ...

  5. C++ part8

    1.volatile关键字 在C++中,对volatile修饰的对象的访问,有编译器优化上的副作用: 不允许被编译器优化,提供特殊地址的稳定访问(只从内存中读取). 有序性,编译器进行优化时,不能把对 ...

  6. P1337 [JSOI2004]平衡点(模拟退火)题解

    题意: 如图:有n个重物,每个重物系在一条足够长的绳子上.每条绳子自上而下穿过桌面上的洞,然后系在一起.图中X处就是公共的绳结.假设绳子是完全弹性的(不会造成能量损失),桌子足够高(因而重物不会垂到地 ...

  7. SPOJ PHRASES Relevant Phrases of Annihilation(后缀数组 + 二分)题解

    题意: 给\(n\)个串,要你求出一个最长子串\(A\),\(A\)在每个字串至少都出现\(2\)次且不覆盖,问\(A\)最长长度是多少 思路: 后缀数组处理完之后,二分这个长度,可以\(O(n)\) ...

  8. windows脚本bat编程:WIN10脚本自动启动虚拟环境中的jupyter

    python编程对各种扩展包的版本依赖较严格,为了解决版本差异,通用情况下会使用virtualenv创建的虚拟环境来独立应用.那么每次使用的时候就需要启动虚拟环境,如果每次都是手工启动,每次输入几条命 ...

  9. Web 前端页面性能监控指标

    Web 前端页面性能监控指标 性能监控 / 性能指标 / 性能优化 白屏时间计算 FCP 白屏时间:从浏览器输入地址并回车后到页面开始有内容的时间: 首屏时间计算 FMP 首屏时间:从浏览器输入地址并 ...

  10. tree traversal

    tree traversal tree 遍历 中序,顺序,左中右 先序,先父节点,中左右 后序,先子节点,左右中 二叉搜索树 "use strict"; /** * * @auth ...