首页 > 解决方案 > 构建使用协议缓冲区(无 APK)的 Android 可执行 gRPC 服务器

问题描述

我从这里编译了 gRPC Android 示例。

我想从 adb shell 将程序作为可执行文件运行。

将这些行添加到grpc-helloworld.cc

#include <iostream>

int main() {
  std::cout << "qwerty" << std::endl;
  return 0;
}

这些行对其CMakeLists.txt

add_executable(avocado
    src/main/cpp/grpc-helloworld.cc)

target_include_directories(avocado
  PRIVATE ${HELLOWORLD_PROTO_HEADERS})

target_link_libraries(avocado
  helloworld_proto_lib
  android
  ${log-lib})

然后我推送生成的可执行文件和库文件并尝试运行它:

LD_LIBRARY_PATH=. ./avocado

我收到以下错误:

[libprotobuf FATAL /home/buga/grpc/third_party/protobuf/src/google/protobuf/stubs/common.cc:79] 这个程序是针对 Protocol Buffer 运行时库的 3.0.0 版本编译的,它与安装版本(3.5.1)。联系程序作者获取更新。如果您自己编译程序,请确保您的标头来自与链接时库相同版本的 Protocol Buffers。(“out/soong/.intermediates/frameworks/av/drm/libmediadrm/libmediadrm/android_arm64_armv8-a_kryo300_shared_core/gen/proto/frameworks/av/drm/libmediadrm/protos/plugin_metrics.pb.cc”中的版本验证失败。)终止带有 google::protobuf::FatalException 类型的未捕获异常:该程序是针对 Protocol Buffer 运行时库的 3.0.0 版本编译的,这与安装的版本(3.5.1)不兼容。联系程序作者获取更新。如果您自己编译程序,请确保您的标头来自与链接时库相同版本的 Protocol Buffers。(“out/soong/.intermediates/frameworks/av/drm/libmediadrm/libmediadrm/android_arm64_armv8-a_kryo300_shared_core/gen/proto/frameworks/av/drm/libmediadrm/protos/plugin_metrics.pb.cc”中的版本验证失败。)中止

我究竟做错了什么?

我们意识到有一个名为libprotobuf-cpp-full.soand的 protobuf 库libprotobuf-cpp-lite.so版本,它们的版本似乎是 3.0.0。这与我们编译成静态库或共享库的版本(3.5.1)冲突。

标签: androidc++android-ndkprotocol-buffersgrpc

解决方案


我不太确定为什么会这样。一旦链接器加载helloworld_proto_lib,它会覆盖所有加载的 protobuf 符号,并且由于某种原因,与您无关的另一个库会使您的程序崩溃。但这并不能告诉你任何新的东西。

这是解决此问题的一种方法:

1. grpc-helloworld.cc 的变化

制作 main extern "C",并可能更改其名称。例如:

 extern "C" int my_main() {
  std::cout << "qwerty" << std::endl;
  return 0;
}

2.添加文件grpc-avocado.cc

这将包含可执行文件的实际 main,它将动态加载库helloworld_proto_libgrpc-helloworld. 这是如何做到的:

#include <iostream>
#include <android/dlext.h>
#include <dlfcn.h>

int main() {
  android_dlextinfo extinfo;
  extinfo.flags = ANDROID_DLEXT_FORCE_LOAD;

  void* proto_lib = android_dlopen_ext("/path/to/libhelloworld_proto_lib.so", RTLD_LAZY, &extinfo);
  void* helloworld = dlopen("/path/to/libgrpc-helloworld.so", RTLD_LAZY);
  int (*my_main)() = (int (*)())dlsym(helloworld, "my_main");

  return my_main();
}

android_dlopen_ext的函数#include <android/dlext.h>及其标志参数在此处进行了描述:https ://developer.android.com/ndk/reference/group/libdl 。在上面的代码中,我们传递了 flag ANDROID_DLEXT_FORCE_LOAD,它被记录为:

设置后,不要使用 stat(2) 检查库是否已经加载。

此标志允许在由于某种原因多个 ELF 文件共享相同文件名的情况下强制加载库(例如,因为已加载的库已被删除和覆盖)。

请注意,如果该库具有与旧库相同的 DT_SONAME 并且某个其他库在其 DT_NEEDED 列表中具有 soname,则第一个将用于解决任何依赖项

我认为粗体字是解释为什么这个解决方案有效的原因。

3.更改CMakeLists.txt

由于您将helloworld_proto_lib动态加载,因此您现在可以将其从可执行定义中删除,并且不需要任何 proto 标头:

add_executable(avocado
    src/main/cpp/grpc-avocado.cc)

target_link_libraries(avocado
  android
  ${log-lib})

构建、推送和运行

您现在可以构建、推送可执行文件avocado和两个库libgrpc-helloworld.solibhelloworld_proto_lib.so和运行。你不需要LD_LIBRARY_PATH。祝你项目的其余部分好运!


推荐阅读