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
。python
简单的例子:git
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文件只有一个或者说都只在一个目录里,那用这个命令没什么毛病...github
但若是是这种状况,咱们的文件目录以下:api
├── 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的一篇博文)ui
也想过把他俩放到同一个目录...而后bar.proto
中import的代码就要修改,虽然这样能够,但显然是不适合大型的项目。google
而这个大型项目显然就是mediapipe...折磨了我很久。debug
关于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的状况是这样)。
对于这种状况,比较合适的作法是直接使用命令进行生成。
首先找到全部须要编译的.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博客」。
老潘也会整理一些本身的私藏,但愿能帮助到你们,点击神秘传送门获取。