在Flutter的C++代码中使用Protobuf 3——如何编译并Link?

Use Protobuf 3 In Flutter's C++ Code - How To Compile And Link?

Protobuf是一个使用非常广泛的库,所以我想在Flutter的C++代码中使用它来序列化和反序列化我的数据。但是,我发现编译和链接protobuf库并不是一件小事......我失败了很多次才找到正确的解决方案,所以我想在这里以问答的方式分享。

准备

首先,当然,您需要生成您的 protobuf 文件。这超出了本教程的范围,因此您可以查看 the official guide。假设您已经生成了 hello.pb.hhello.pb.cc

iOS

如果您正在开发使用需要 protobuf 的 C++ 代码的 package/plugin,请将以下内容添加到您的 xxx.podspec。如果是你的may代码需要protobuf,也是类似的。

Pod::Spec.new do |s|
  ...normal things...

  s.pod_target_xcconfig = {
    'HEADER_SEARCH_PATHS' => '$(SRCROOT)/Protobuf-C++/src',
  }
  
  s.dependency 'Protobuf-C++', '~> 3.18.0'
end

此外,您需要在主项目的 Podfile 中添加以下内容。比如你开发一个包,被10个项目使用,每个项目都需要添加下面的section。

post_install do |installer|
  ...normal things...
  
  installer.pods_project.targets.each do |target|
    # print "target=", target, "\n"
    if target.name == "Protobuf-C++"
      target.build_configurations.each do |config|
        config.build_settings['HEADER_SEARCH_PATHS'] = '$(SRCROOT)/Protobuf-C++/src'
      end
    end
  end
end

然后清理并构建项目并享受!

备注:为什么我需要破解 HEADER_SEARCH_PATHS:最初它出错并说找不到 protobuf headers。 This solution suggests that we add the paths manually by hand after each pod install. Moreover, this improved solution 建议我们可以通过添加几行代码来自动化该过程。这就是我们上面所做的。

另一种方式

This link 基本上工作得很好。虽然是在Cardboard的SDK教程下,但是这个网页几乎没有讲到Cardboard-specific的东西,只讲了protobuf。因此,请随时遵循它。除了最后一步(“复制文件”)之外的所有步骤之后,您将得到:include/google/*lib/libprotobuf-lite.a.

开始之前,请不要忘记make distclean 清理所有内容(如果您已经完成某些操作)。

备注1:如果您在linking时看到错误,提示“Undefined symbols”和“ld: warning: ignoring file”,您可以按照this link解决。

备注2:该教程只创建了libprotobuf-lite.a。如果需要libprotobuf.a,可以类推:lipo $build_dir/arch/arm64/lib/libprotobuf.a $build_dir/arch/armv7/lib/libprotobuf.a -create -output $build_dir/lib/libprotobuf.a.

然后,您可以将 .a 和 headers 放入您的 ios xcode 项目中。这个我还没试过,因为我用的是上面的方法。

Android

获取库和headers

比iOS复杂一点。

开始之前,请不要忘记make distclean 清理所有内容(如果您已经完成某些操作)。

先跟着the same link as the approach to generating your own .a and headers in iOSgit clonegit checkoutgit submodule./autogen.sh,但就此打住,不再继续。

接下来,您需要按照this answer稍微修改一下代码——否则编译会失败。请参考link使用ltmain.sh.patch打补丁。 (至于fuse-ld flag我已经做了,不用你做任何事。)

下面是配置和编译,如下所示。

export NDKDIR=/Users/tom/Library/Android/sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/darwin-x86_64
export SYSROOT=$NDKDIR/sysroot

./configure \
--prefix=/Users/tom/RefCode/libprotobuf/android \
--host=arm-linux-androideabi \
--with-sysroot="${SYSROOT}" \
--enable-shared \
--enable-cross-compile \
--with-protoc=protoc \
"CC=$NDKDIR/bin/armv7a-linux-androideabi26-clang" \
CFLAGS="-fPIC -fuse-ld=bfd -march=armv7-a -D__ANDROID_API__=26" \
"CXX=$NDKDIR/bin/armv7a-linux-androideabi26-clang++" \
CXXFLAGS="-fPIC -fuse-ld=bfd -frtti -fexceptions -march=armv7-a -D__ANDROID_API__=26" \
"RANLIB=$NDKDIR/bin/x86_64-linux-android-ranlib" \
"C_COMPILER_RANLIB=$NDKDIR/bin/x86_64-linux-android-ranlib" \
"CXX_COMPILER_RANLIB=$NDKDIR/bin/x86_64-linux-android-ranlib" \
"AR=$NDKDIR/bin/x86_64-linux-android-ar" \
LIBS="-llog -lz -lc++_static";
make -j8 && make install

备注:以上命令与this post相似但不等同,因为post中使用的NDK独立工具链已被弃用。当然,你可能需要改变你的版本,比如你自己的ndk的路径,你的ndk版本等等

备注:您可能还需要知道上面使用的命名规则。比如为什么有时候是llvm,为什么有时候是darwin,为什么是armv7a。它们被详细描述 in the official guide.

备注:当您使用 Gradle.

编译 android 应用程序时遇到 libprotobuf.a: no archive symbol table (run ranlib) 问题时,this link 也很有用

备注:如果没有-fPIC,会出现requires unsupported dynamic reloc R_ARM_REL32; recompile with -fPIC这样的错误。所以我跟着this link来解决它。

备注:如果看到error: undefined reference to 'stderr'这样的错误,可能需要把你的minSdkVersion调大一些,比如改成23。ref

备注:更换东西后,可能需要多次清洗。例如,gradle sync,gradle clean,make distclean 等

the guide类似,下一步是make -j8,然后是make install。然后你就完成了,你会看到一些库,比如 libprotobuf-lite.a 以及包括 headers.

在你的项目中使用它

将以下内容添加到 CMakeLists.txt

set(OPENCV_BASE_DIR "/some/absolute/path/to/your/base/dir/that/has/built/just/now")

message("PROTOBUF_BASE_DIR: ${PROTOBUF_BASE_DIR}")

set(PROTOBUF_INCLUDE_DIR "${PROTOBUF_BASE_DIR}/include/")
set(PROTOBUF_STATIC_LIB_DIR "${PROTOBUF_BASE_DIR}/lib")

if (NOT EXISTS ${PROTOBUF_INCLUDE_DIR})
    message(FATAL_ERROR "PROTOBUF_INCLUDE_DIR=${PROTOBUF_INCLUDE_DIR} does not exist - have you provided the correct PROTOBUF_BASE_DIR=${PROTOBUF_BASE_DIR}")
endif ()

include_directories(${PROTOBUF_INCLUDE_DIR})

add_library(protobuf-lite STATIC IMPORTED)
set_target_properties(protobuf-lite PROPERTIES IMPORTED_LOCATION ${PROTOBUF_STATIC_LIB_DIR}/libprotobuf-lite.a)

add_library(protobuf STATIC IMPORTED)
set_target_properties(protobuf PROPERTIES IMPORTED_LOCATION ${PROTOBUF_STATIC_LIB_DIR}/libprotobuf.a)

target_link_libraries(yourapp
        ...others...

        protobuf
        )

由于我在上面的代码中只为 armv7 而​​不是 armv8 构建,我们可以添加一个 abi 过滤器,如下所示。或者,您也可以为 armv8 构建并将它们合并到一个胖库中,就像我们在 ios.

中所做的那样

build.gradle:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                abiFilters 'armeabi-v7a'
            }
        }
    }
}

奖励:MacOS

获取库和headers

如果您还想在 mac 计算机上使用 C++ 代码(使用 protobuf)怎么办?

开始之前,请不要忘记make distclean 清理所有内容(如果您已经完成某些操作)。

类似于this link,你只需要将你的./configure改为:

./configure CC=clang CXX="clang++ -std=c++11 -stdlib=libc++" CXXFLAGS="-O3" --disable-shared

其他的和the guide几乎一样,比如下载、配置、make最后sudo make install(注意这里需要sudo)。

您将在系统的 include 目录中看到 headers,在系统的 lib 文件夹中看到 libs。

在你的项目中使用它

如果您使用的是 CMake,它主要是标准的。首先,将 path/to/your/hello.pb.cc 添加到您的 add_executable 中。其次,将 /usr/local/lib/libprotobuf.a 添加到您的 target_link_libraries。完整示例:

add_executable(app
        ./main.cpp
        path/to/your/hello.pb.cc
        ... others ...
        )
        
target_link_libraries(app
        /usr/local/lib/libprotobuf.a
        ... others ...
        )

备注:libprotobuf-lite.a有时不够用,所以你需要link使用big lib而不是lite lib。