• 在 CMake 项目中使用 protobuf


    简介

    protobuf 只需要我们定义 .proto 格式的数据结构,然后使用 protobuf 编译器生成指定语言的代码,然后我们就可以在指定的语言中使用这个数据结构了。protobuf 的一大好处就是数据结构的序列化和反序列化,这些自定义的数据结构经过序列化之后就可以通过网络、本地系统等方式传给其他进程使用,并且因为 protobuf 有多语言支持,这些数据结构还可以通过序列化和反序列化来支持混合语言编程(比如 C++ 底层和 python 前端)。

    为了用上 protobuf 有几种方式:

    • 手动调用 protoc 来编译文件,然后引入自己的项目。
    • 使用 CMake 提供的 find_package 脚本找到 protobuf,得到一些变量。
    • 使用 CMake 下载指定版本 protobuf,源码编译 protobuf,然后用编译生成的 protoc 来编译。

    第一种方法,不够自动,手动的要素太多;第二种方法,使用系统安装的 protoc,会存在版本差异,另外 ubuntu 上 apt 安装的是 3.0.0,之前还遇到过编译成 Java 后出现 “局部变量” 和 message 的属性冲突的 BUG,更新了版本之后就没有问题了。因此,本文将会介绍如何使用第三种方法在 CMake 中引入 protobuf。本文使用的代码主要是从 oneflow 复制粘贴过来的hhh.

    实施

    第三种方法分为四个步骤。

    1. 源码编译 protobuf 的依赖:zlib
    2. 源码编译 protobuf,前两步使用 ExternalProject_Add 指令来编译
    3. 使用编译生成的 protoc 来编译 .proto 文件,oneflow 里面写了一个函数来编译所有 .proto 文件,函数里面通过 add_custom_command 来调用 protoc 进行编译
    4. 将所有 .proto 生成的文件编译成一个静态链接库,再将编译可执行文件,将静态链接库链接进去

    代码地址:https://github.com/zzk0/cmake_cpp_cuda/tree/master/cpp/protobuf

    代码结构如下所示。我是在一个大的 CMake 项目中,通过 add_sub_directory 来加入这个子项目。如果要单独用这个子项目,需要加上 cmake 最低版本的指令。其中 third_party 下面是使用了第三方的依赖,通过 ExternalProject_Add 指令来下载、校验、解压、编译。proto2cpp.cmake 里面是一个函数,将 .proto 编译成 .cpp 文件,这个函数会通过 set 指令设置 PARENT_SCOPE 中的变量,从而导出相关的依赖。

    编译链接可执行文件

    我们主要看看最外面的 CMakeLists.txt,其他三个文件就需要你具体去看代码了,其实就是调用 ExternalProject_Add 和函数。

    我们将项目的 .proto 文件编译成 .cpp 之后,再编译一次成静态链接库。需要特别注意的是需要链接 Threads,如果不链接会导致 core_dump

    project(protobuf-cpp)
    
    set(THIRD_PARTY_DIR "${PROJECT_BINARY_DIR}/third_party_install"
            CACHE PATH "Where to install third party headers and libs")
    
    # include 指令里面的 set 操作的变量作用域就是在这个文件,
    # 可以类比 c++ 的 include 相当于把那里面的东西 include 进来
    set(cmake_dir ${PROJECT_SOURCE_DIR}/cmake)
    list(APPEND CMAKE_MODULE_PATH ${cmake_dir})
    list(APPEND CMAKE_MODULE_PATH ${cmake_dir}/third_party)
    
    # 最好设置代理, 需要从 github 下载源代码
    include(zlib)
    include(protobuf)
    include(proto2cpp)
    # protobuf 需要 link threads, 否则会报错
    find_package(Threads)
    
    file(GLOB PROTO_FILES ${PROJECT_SOURCE_DIR}/*.proto)
    foreach(proto_name ${PROTO_FILES})
        file(RELATIVE_PATH proto_rel_name ${PROJECT_SOURCE_DIR} ${proto_name})
        list(APPEND REL_PROTO_FILES ${proto_rel_name})
    endforeach()
    PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${PROJECT_SOURCE_DIR} ${REL_PROTO_FILES})
    add_library(proto_lib STATIC ${PROTO_SRCS} ${PROTO_HDRS})
    # 这里设置为 PUBLIC 是因为在链接生成 exe 的时候, 需要这些 include
    # include 的本质就是将那些东西复制进来, 所以 main.cpp 上面就会 include PROTOBUF_INCLUDE_DIR
    # 因此需要设置为 PUBLIC 才行
    target_include_directories(proto_lib PUBLIC ${PROTOBUF_INCLUDE_DIR})
    target_link_libraries(proto_lib PRIVATE ${PROTOBUF_STATIC_LIBRARIES} Threads::Threads)
    
    add_executable(${PROJECT_NAME} main.cpp)
    target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
    target_link_libraries(${PROJECT_NAME} PRIVATE proto_lib)
    

    protobuf 简介

    protobuf 的一大特点就是通过 “代码生成” 数据结构类的方式来序列化、反序列化二进制数据。这些数据结构类可以实例化,里面还提供了一些方法用于获取数据、设置数据等。

    例子

    以 Google 官方的教程为例子。这个文件定义了 AddressBook,一个 AddressBook 是由多个 Person 组成的,每个 Person 有若干种属性:名字、号码、邮箱、多个手机号。下面的例子基本展示了 protobuf 数据定义的语法,和 C++ Java 是相似的。

    syntax = "proto2";
    
    package tutorial;
    
    message Person {
      optional string name = 1;
      optional int32 id = 2;
      optional string email = 3;
    
      enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
      }
    
      message PhoneNumber {
        optional string number = 1;
        optional PhoneType type = 2 [default = HOME];
      }
    
      repeated PhoneNumber phones = 4;
    }
    
    message AddressBook {
      repeated Person people = 1;
    }
    

    代码生成规则

    地址:https://developers.google.com/protocol-buffers/docs/reference/cpp-generated

    操作 protobuf 对象的时候,看返回值和方法前面大概就知道是干嘛的了。比如有的会返回指针,那么你可以修改它,比如 mutable 开头的方法,或者 repeated 属性才有的 add 开头的方法;有的方法是 const 方法,这意味着你只能读取数据。

    protobuf 序列化和反序列化都是二进制数据,所以即使是 ParseFromString 方法,也是要二进制 string 才行,不可以使用 DebugString(),或者你可以看懂的 string。

  • 相关阅读:
    CodeForces 660D Number of Parallelograms
    【POJ 1082】 Calendar Game
    【POJ 2352】 Stars
    【POJ 2481】 Cows
    【POJ 1733】 Parity Game
    【NOI 2002】 银河英雄传说
    【NOI 2015】 程序自动分析
    【POJ 1704】 Georgia and Bob
    【HDU 2176】 取(m堆)石子游戏
    【SDOI 2016】 排列计数
  • 原文地址:https://www.cnblogs.com/zzk0/p/15583430.html
Copyright © 2020-2023  润新知