Protobuf在Cmake中的正确使用方法详解

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

到此这篇关于Protobuf在Cmake中的正确使用方法的文章就介绍到这了,更多相关Protobuf使用Cmake内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Protobuf的简要介绍及使用详解

    一.protobuf的应用场景 在官方文档中可以看到 protocol buffers 是一种语言无关.平台无关.可扩展的序列化结构数据的方法,它可用于(数据)通信协议.数据存储等. Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍).更快(20 ~ 100倍).更为简单. 可以看到protobuf 与json相比具有 压缩比高 .解压缩速度更快的优点 二.protobuf的使用 protobuf在使用上较为

  • 详解Google Protobuf简明教程

    Protobuf是什么 Protobuf实际是一套类似Json或者XML的数据传输格式和规范,用于不同应用或进程之间进行通信时使用.通信时所传递的信息是通过Protobuf定义的message数据结构进行打包,然后编译成二进制的码流再进行传输或者存储. Protobuf的优点 相比较而言,Protobuf有如下优点: 足够简单 序列化后体积很小:消息大小只需要XML的1/10 ~ 1/3 解析速度快:解析速度比XML快20 ~ 100倍 多语言支持 更好的兼容性,Protobuf设计的一个原则就

  • Protobuf在Cmake中的正确使用方法详解

    Protobuf是google开发的一个序列化和反序列化的协议库,我们可以自己设计传递数据的格式,通过.proto文件定义我们的要传递的数据格式.例如,在深度学习中常用的ONNX交换模型就是使用.proto编写的.我们可以通过多种前端(MNN.NCNN.TVM的前端)去读取这个.onnx这个模型,但是首先你要安装protobuf. 在之前的博文中已经简单介绍了onnx,其中onnx.proto就代表了onnx模型的基本数据结构.一般来说,protobuf经常搭配Cmake使用,Cmake有官方的

  • Vue 中使用 typescript的方法详解

    什么是typescript typescript 为 javaScript的超集,这意味着它支持所有都JavaScript都语法.它很像JavaScript都强类型版本,除此之外,它还有一些扩展的语法,如interface/module等. typescript 在编译期会去掉类型和特有语法,生成纯粹的JavaScript. Typescript 5年内的热度随时间变化的趋势,整体呈现一个上升的趋势.也说明ts越来越️受大家的关注了. 安装typescript npm install -g ty

  • Vue3中使用Supabase Auth方法详解

    目录 引言 安装Supabase 设置Supabase 创建一个AuthUser组合 创建页面 注册.vue EmailConfirmation.vue 登录.vu ForgotPassword.vue Me.vue login() loginWithSocialProvider() logout() isLoggedIn() register() update() sendPasswordResetEmail() 观察Auth状态的变化 测试东西 注销 家庭作业 总结 引言 Supabase是

  • python编程之requests在网络请求中添加cookies参数方法详解

    哎,好久没有学习爬虫了,现在想要重新拾起来.发现之前学习爬虫有些粗糙,竟然连requests中添加cookies都没有掌握,惭愧.废话不宜多,直接上内容. 我们平时使用requests获取网络内容很简单,几行代码搞定了,例如: import requests res=requests.get("https://cloud.flyme.cn/browser/index.jsp") print res.content 你没有看错,真的只有三行代码.但是简单归简单,问题还是不少的. 首先,这

  • Android通过json向MySQL中读写数据的方法详解【读取篇】

    本文实例讲述了Android通过json向MySQL中读取数据的方法.分享给大家供大家参考,具体如下: 首先 要定义几个解析json的方法parseJsonMulti,代码如下: private void parseJsonMulti(String strResult) { try { Log.v("strResult11","strResult11="+strResult); int index=strResult.indexOf("[");

  • java 中enum的使用方法详解

    java 中enum的使用方法详解 enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中. 下面是我在使用 enum 过程中的一些经验和总结. 原始的接口定义常量 public interface IConstants { String MON = "Mon"; String TUE = "Tue"; String WED = "Wed"; String THU = "Thu

  • Android 中RxPermissions 的使用方法详解

    Android 中RxPermissions 的使用方法详解 以请求拍照.读取位置权限为例 module的build.gradle: compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' compile 'io.reactivex.rxjava2:rxjava:2.0.5' AndroidManifest.xml: <uses-permission android:name="android.permission.AC

  • Android中XUtils3框架使用方法详解(一)

    xUtils简介 xUtils 包含了很多实用的android工具. xUtils 支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响... xUitls 最低兼容android 2.2 (api level 8) 今天给大家带来XUtils3的基本介绍,本文章的案例都是基于XUtils3的API语法进行的演示.相信大家对这个框架也都了解过, 下面简单介绍下XUtils3的一些基本知识. XUtils3一共有4大功能:注解模块,网络

  • Android 中Context的使用方法详解

    Android 中Context的使用方法详解 概要: Context字面意思是上下文,位于framework package的android.content.Context中,其实该类为LONG型,类似Win32中的Handle句柄.很多方法需要通过 Context才能识别调用者的实例:比如说Toast的第一个参数就是Context,一般在Activity中我们直接用this代替,代表调用者的实例为Activity,而到了一个button的onClick(View view)等方法时,我们用t

  • Android通过json向MySQL中读写数据的方法详解【写入篇】

    本文实例讲述了Android通过json向MySQL中写入数据的方法.分享给大家供大家参考,具体如下: 先说一下如何通过json将Android程序中的数据上传到MySQL中: 首先定义一个类JSONParser.Java类,将json上传数据的方法封装好,可以直接在主程序中调用该类,代码如下 public class JSONParser { static InputStream is = null; static JSONObject jObj = null; static String j

随机推荐