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 )