gRPC + flatbuffers を自前 C++ プロジェクトに取り込むときのメモ


目的

Android(C++ or Java/Kotlin) と PC(C++) の間で 3D データや画像データなどを非同期メッセージ通信したい.

今までは json-rpc + json shcema + http or websocket でやっていましたが,
flatbuffer でメッセージをシリアライズし, gRPC を使えば, 大規模データが扱いやすく, メッセージのエラーチェックや非同期通信が楽にできるかと思いました.

ビルド

flatbuffers の flatc compiler では, protobuf のソースコードの一部を利用しているため, gRPC のライブラリと組み合わせるときは gRPC 側のバージョンを flatbuffers が利用しているバージョンと合わせる必要があります.

今回(2018 年 3 月 10 日時点)では, gRPC v1.15.1 でした.

cmake の例は以下のような感じになります.
grpc も flatbuffers も git submodule で取り込んでいるものとします.

  set(GRPC_AS_SUBMODULE On)

  if(GRPC_AS_SUBMODULE)
    # One way to build a projects that uses gRPC is to just include the entire
    # gRPC project tree via "add_subdirectory". This approach is very simple to
    # use, but the are some potential disadvantages: * it includes gRPC's
    # CMakeLists.txt directly into your build script without and that can make
    # gRPC's internal setting interfere with your own build. * depending on
    # what's installed on your system, the contents of submodules in gRPC's
    # third_party/* might need to be available (and there might be additional
    # prerequisites required to build them). Consider using the gRPC_*_PROVIDER
    # options to fine-tune the expected behavior.
    #
    # A more robust approach to add dependency on gRPC is using cmake's
    # ExternalProject_Add (see cmake_externalproject/CMakeLists.txt).

    # Include the gRPC's cmake build (normally grpc source code would live in a
    # git submodule called "third_party/grpc", but this example lives in the
    # same repository as gRPC sources, so we just look a few directories up)
    add_subdirectory(${CMAKE_SOURCE_DIR}/externals/grpc
                     ${CMAKE_CURRENT_BINARY_DIR}/grpc EXCLUDE_FROM_ALL)
    message(STATUS "Using gRPC via add_subdirectory.")

    # After using add_subdirectory, we can now use the grpc targets directly
    # from this build. set(_PROTOBUF_LIBPROTOBUF libprotobuf)
    # set(_PROTOBUF_PROTOC $<TARGET_FILE:protoc>)
    set(_GRPC_GRPCPP_UNSECURE grpc++_unsecure)
    set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:grpc_cpp_plugin>)
  else()
    # This branch assumes that gRPC and all its dependencies are already
    # installed on this system, so they can be located by find_package().

    # Find Protobuf installation Looks for protobuf-config.cmake file installed
    # by Protobuf's cmake installation.
    set(protobuf_MODULE_COMPATIBLE TRUE)
    find_package(Protobuf CONFIG REQUIRED)
    message(STATUS "Using protobuf ${protobuf_VERSION}")

    set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
    set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)

    # Find gRPC installation Looks for gRPCConfig.cmake file installed by gRPC's
    # cmake installation.
    find_package(gRPC CONFIG REQUIRED)
    message(STATUS "Using gRPC ${gRPC_VERSION}")

    set(_GRPC_GRPCPP_UNSECURE gRPC::grpc++_unsecure)
    set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
  endif()

  # Proto file get_filename_component(hotreload_proto
  # "${CMAKE_SOURCE_DIR}/src/hotreload/hotreload.proto" ABSOLUTE)
  # get_filename_component(hotreload_proto_path "${hotreload_proto}" PATH)

  # Generated sources set(hotreload_proto_srcs
  # "${CMAKE_CURRENT_BINARY_DIR}/hotreload.pb.cc") set(hotreload_proto_hdrs
  # "${CMAKE_CURRENT_BINARY_DIR}/hotreload.pb.h") set(hotreload_grpc_srcs
  # "${CMAKE_CURRENT_BINARY_DIR}/hotreload.grpc.pb.cc") set(hotreload_grpc_hdrs
  # "${CMAKE_CURRENT_BINARY_DIR}/hotreload.grpc.pb.h") add_custom_command(
  # OUTPUT "${hotreload_proto_srcs}" "${hotreload_proto_hdrs}"
  # "${hotreload_grpc_srcs}" "${hotreload_grpc_hdrs}" COMMAND
  # ${_PROTOBUF_PROTOC} ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" --cpp_out
  # "${CMAKE_CURRENT_BINARY_DIR}" -I "${hotreload_proto_path}" --plugin=protoc-
  # gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}" "${hotreload_proto}" DEPENDS
  # "${hotreload_proto}")

  # Include generated *.pb.h files
  # include_directories("${CMAKE_CURRENT_BINARY_DIR}")

  # some protobuf headers are still required even when using flatbuffer serialization.
  include_directories("${CMAKE_SOURCE_DIR}/externals/grpc/third_party/protobuf/src/")

  # gRPC header files.
  include_directories("${CMAKE_SOURCE_DIR}/externals/grpc/include")

  #
  # Experimental. Use flatbuffers for serialization
  #

  # [flatbuffers]
  set(FLATBUFFERS_BUILD_TESTS Off CACHE INTERNAL "" FORCE)
  set(FLATBUFFERS_BUILD_FLATHASH Off CACHE INTERNAL "" FORCE)
  set(FLATBUFFERS_INSTALL Off CACHE INTERNAL "" FORCE)

  add_subdirectory(${CMAKE_SOURCE_DIR}/externals/flatbuffers)

  # Flatbuffer header files.
  include_directories("${CMAKE_SOURCE_DIR}/externals/flatbuffers/include")

  # Assume `$BUILD/externals/flatbuffers/flatc`
  set(_FLATBUFFER_FLATC $<TARGET_FILE:flatc>)

  # Flatbuffer file
  get_filename_component(hotreload_fbs
                         "${CMAKE_SOURCE_DIR}/src/hotreload/hotreload.fbs"
                         ABSOLUTE)
  get_filename_component(hotreload_fbs_path "${hotreload_fbs}" PATH)

  # Generated sources
  set(hotreload_fb_hdrs "${CMAKE_CURRENT_BINARY_DIR}/hotreload_generated.h")
  set(hotreload_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/hotreload.grpc.fb.cc")
  set(hotreload_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/hotreload.grpc.fb.h")
  add_custom_command(OUTPUT "${hotreload_fb_hdrs}" "${hotreload_grpc_srcs}"
                            "${hotreload_grpc_hdrs}"
                     COMMAND ${_FLATBUFFER_FLATC}
                             ARGS -o "${CMAKE_CURRENT_BINARY_DIR}"
                             --grpc -c "${hotreload_fbs}"
                     DEPENDS "${hotreload_fbs}")

  set(EXPORTER_LOGIC_PLUGIN_NAME "exporter_logic")

  add_library(${EXPORTER_LOGIC_PLUGIN_NAME}
              SHARED
              ./src/hotreload/HotReloadExporterLogic.cc
              ./src/hotreload/cyhair-writer.cc
              ${hotreload_fb_srcs}
              ${hotreload_grpc_srcs})

  # Must link with OpenMaya otherwise undefined symbol error happens when
  # loading dll(Linux only?).
  target_link_libraries(${EXPORTER_LOGIC_PLUGIN_NAME}
                        ${_GRPC_GRPCPP_UNSECURE}
                        flatbuffers
                        AdskXGen
                        OpenMaya
                        OpenMayaAnim # For SkinCluster
                        OpenMayaUI # For progress window
                        Foundation)

gRPC は, flatbuffer でシリアライズする場合でも protobuf のヘッダの一部を利用します. システムでインストールされた protobuf を使わない

まとめ

やりたいことに対して, gRPC はライブラリがおおきく依存関係がたくさんあってビルドがつらい.