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

在之前的博文中已经简单介绍了,其中就代表了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中使用了大量的protobuf技术来表示图结构,而且mediapipe原生并不是采用cmake来构建项目,而是使用google自家研发的,这个项目构建系统我就不评价了,而现在我需要使用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 ".+/(.+)\\..*" "\" file_name ${fil})
  string(regex replace "(.+)\${file_name}.*" "\" 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()

参考链接

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