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

作者:老潘博客 时间:2024-01-02 01:26:12 

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来对其进行构建。

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

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

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

而且每个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

来源:https://www.cnblogs.com/bigoldpan/archive/2021/03/08/14502873.html

标签:Protobuf,使用,Cmake
0
投稿

猜你喜欢

  • 完美解决TensorFlow和Keras大数据量内存溢出的问题

    2021-09-23 07:07:33
  • python中使用矢量化替换循环详解

    2023-08-27 00:51:01
  • 利用Python找出删除自己微信的好友并将他们自动化删除

    2022-05-23 00:10:13
  • 解决Python传递中文参数的问题

    2021-04-10 09:00:07
  • Django跨域请求CSRF的方法示例

    2021-07-13 21:09:34
  • 设计手机端应用时的一些建议

    2011-05-14 16:45:00
  • Python在线和离线安装第三方库的方法

    2023-08-24 19:37:11
  • 解决python3 整数数组转bytes的效率问题

    2023-08-09 19:39:41
  • 如何改良你的CSS代码编写结构

    2008-09-29 16:03:00
  • 使用Keras加载含有自定义层或函数的模型操作

    2022-12-25 19:29:29
  • Python用Pillow(PIL)进行简单的图像操作方法

    2022-06-10 10:29:49
  • python pandas 数据排序的几种常用方法

    2021-10-02 06:25:01
  • windows下python 3.9 Numpy scipy和matlabplot的安装教程详解

    2021-12-20 13:20:47
  • python 通过视频url获取视频的宽高方式

    2022-06-04 08:10:58
  • python linecache读取行更新的实现

    2021-01-26 01:33:06
  • Python PyQt拖动控件对齐到网格的方法步骤

    2022-05-04 19:25:00
  • Python 如何实现访问者模式

    2021-08-10 20:49:53
  • 设计工作者必须了解的常识

    2008-04-06 13:56:00
  • python实现的自动发送消息功能详解

    2021-12-09 20:45:18
  • python下setuptools的安装详解及No module named setuptools的解决方法

    2022-12-21 00:56:46
  • asp之家 网络编程 m.aspxhome.com