【问题标题】:Android NDK/JNI: Building a shared library that depends on other shared librariesAndroid NDK/JNI:构建依赖于其他共享库的共享库
【发布时间】:2014-03-22 06:39:21
【问题描述】:

我正在编写一个 Android 应用程序,它希望将 JNI 调用放入使用 NDK 内置的共享库中。诀窍是这个共享库调用其他共享库提供的函数。其他共享库是在别处编译的 C 库。

这是我尝试过的:

我的环境: 我在 Eclipse 中工作。我添加了本机支持并拥有一个 jni 库。在那个库中,我有我的代码和一个 \lib 目录,我在其中复制了我的其他 .so 文件。

尝试 #1 Android.mk:告诉它库在哪里

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib1
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib2

include $(BUILD_SHARED_LIBRARY)

这构建得很好,但是当我尝试运行时,我收到错误,表明 dlopen(libnative_lib) 失败,因为它无法加载 libsupport_lib1。

来到这里我发现了这个:

Can shared library call another shared library?

这表示我需要在所有必要的库上调用加载库。太好了!

尝试 #2 首先打开每个库

static {
    System.loadLibrary("support_lib1");
    System.loadLibrary("support_lib2");
    System.loadLibrary("native_lib");
}

同样,这构建得很好,但是当我运行时出现新错误:

无法加载 libsupport_lib1。 findLibrary 返回 null。

现在我们正在取得进展。它不能将库加载到目标。

尝试 #3 将 .so 文件复制到 project/libs/armeabi 中

没用。当 Eclipse 构建时,它删除了我放在那里的文件。

尝试 #4 为每个库创建一个新模块

然后我发现了这个:

Android NDK: Link using a pre-compiled static library

这是关于静态库的,但也许我遇到了类似的问题。要点是我需要为每个库声明一个模块。所以我的新 Android.mk 看起来像这样:

LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib1.so
include $(BUILD_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib2.so
include $(BUILD_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib1
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib/support_lib2

include $(BUILD_SHARED_LIBRARY)

这构建!更棒的是,armeabi 现在有了 sos!甚至 BETTER 我在尝试运行它时收到以下消息(告诉我 support_lib1 和 2 已由 LoadLibrary 打开:

尝试加载 lib /data/app-lib/com.example.tst/libsupport_lib1.so 添加了共享库 /data/app-lib/com.example.tst/libsupport_lib1.so 在 /data/app-lib/com.example.tst/libsupport_lib1.so 中找不到 JNI_OnLoad,跳过初始化

然后…… dlopen 失败:找不到 libnative_lib.so 引用的符号 func_that_exists_in_libsupport_lib.so

编辑:尝试 5:使用 PREBUILT_SHARED_LIBRARY

所以我发现了这个: How can i Link prebuilt shared Library to Android NDK project?

这似乎正是我要问的。他们的回答似乎是'不要使用'build_shared_library',而是'使用PREBUILT_SHARED_LIBRARY

好的,我们试试。

 LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib1.so
include $(PREBUILT_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := $(LOCAL_PATH)/lib/support_lib2.so
include $(PREBUILT_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_SHARED_LIBRARIES := support_lib1 support_lib2

include $(BUILD_SHARED_LIBRARY)

构建...失败!该版本现在抱怨缺少符号。

编辑:尝试 6:展平一切

所以我回到了 NDK 中的预构建文档。它说:

每个预构建库必须声明为构建系统的单个独立模块。这是一个简单的示例,我们假设文件“libfoo.so”与下面的 Android.mk 位于同一目录中:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := libfoo.so
include $(PREBUILT_SHARED_LIBRARY)

请注意,要声明这样一个模块,您实际上只需要以下内容:

为模块命名(此处为“foo-prebuilt”)。这不需要与预建库本身的名称相对应。

将您提供的预构建库的路径分配给 LOCAL_SRC_FILES。像往常一样,路径是相对于您的 LOCAL_PATH 的。

如果您提供共享库,请包括 PREBUILT_SHARED_LIBRARY,而不是 BUILD_SHARED_LIBRARY。对于静态的,使用 PREBUILT_STATIC_LIBRARY。 预构建的模块不会构建任何东西。但是,您的预构建共享库的副本将被复制到 $PROJECT/obj/local,另一个将被复制并剥离到 $PROJECT/libs/。

因此,让我们尝试将所有内容展平以匹配琐碎的示例。我从他们舒适的 /lib 文件夹中复制了我的库,并将它们放在 jni 根目录中。然后我这样做了:

 LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := support_lib1.so
include $(PREBUILT_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := support_lib2.so
include $(PREBUILT_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_SHARED_LIBRARIES := support_lib1 support_lib2

include $(BUILD_SHARED_LIBRARY)

和...同样的错误。此外,我绝对不会看到库文件被复制到 $PROJECT/obj/local。

太好了....现在呢?

【问题讨论】:

  • 一条线索! eclipse 放入我 armeabi 的库不是我试图提供的库的副本。 armeabi 中的 libsupport_lib1.so 与 jni/lib 中的 libsupport_lib1.so 大小不同...所以我做错了什么没有将其复制到正确的位置?
  • 很不幸,库不能直接放在 libs/ 中而没有 eclipse 在构建时将它们丢弃.. 会简单得多.. 我在任何地方都找不到解决方法。
  • 嗨,我也有同样的问题。你解决了吗?如果是这样,你能给我你的解决方案吗?

标签: android linker android-ndk java-native-interface shared-libraries


【解决方案1】:

您的问题在于命名约定。 NDK 和 Android 坚持共享库名称始终以 lib 开头。否则,库将无法正确链接,无法正确复制到libs/armeabi文件夹,也不会安装在设备上(正确复制到/data/data/package/lib目录。

如果您将support_lib1.so 重命名为libsupport_1.so 并将support_lib2.so 重命名为libsupport_2.so,并将这两个文件放在jni/lib 目录中,那么您的尝试#5 只需稍作更改即可工作:

LOCAL_PATH := $(call my-dir)

#get support_lib1
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib1
LOCAL_SRC_FILES        := lib/libsupport_1.so
include $(PREBUILT_SHARED_LIBRARY)

#get support_lib2
include $(CLEAR_VARS)
LOCAL_MODULE           := support_lib2
LOCAL_SRC_FILES        := lib/libsupport_2.so
include $(PREBUILT_SHARED_LIBRARY)

#build native lib
include $(CLEAR_VARS)    
LOCAL_MODULE           := native_lib
LOCAL_SRC_FILES        := native_lib.cpp

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog
LOCAL_SHARED_LIBRARIES := support_lib1 support_lib2

include $(BUILD_SHARED_LIBRARY)

顺便说一句,我认为你不需要这个-L$(SYSROOT)/../usr/lib

PS别忘了更新 Java 端:

static {
    System.loadLibrary("support_lib1");
    System.loadLibrary("support_lib2");
    System.loadLibrary("native_lib");
}

【讨论】:

  • 现在 gradle 提供了实验性插件,我们可以在 build.gradle 文件中配置所有东西。您能否提供上述 Android.mk 文件的 build.gradle 变体
  • 到目前为止我还没有理解的是如何设置它,以便它使用正确的 .so 作为平台目标。它只允许您选择一个 .so,然后将其分发给所有 armeabi、armeabi-v7a、arm64-v8a 等。如果我们包含的源静态库已经隔离到这些平台中,我们如何将它们传递给这些平台?
  • @JaySnayder : android-xx 没有自动分割共享库的机制,但这很少需要,因为一般来说,NDK 是向后兼容的。这意味着,一个在 Gingerbread 上工作的库仍然可以在 Nogut 上工作。存在一些边缘情况,当您将 APK 拆分为具有不同目标 SDK 的单独版本时,提供不同的 .so 变体是合理的。例如,对于 Lolliop 及更高版本,您不需要 armeabi。
【解决方案2】:

不确定这是否正是您所在的位置,但这是我对这类事情的了解。

  1. 使每个预构建的库都有自己独立的 Makefile。 Android.mk 中的多个目标往往会变得不稳定。伤心。
  2. 使用$(call import-add-path)$(call import-module) 包含每个make 文件
  3. 使用 LOCAL_EXPORT_ 系列变量尽可能多地从预构建的 make 文件中导出。

预建共享库 Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := my_module_name

MY_LIBRARY_NAME := shared_library_name

### export include path
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include

### path to library
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/lib$(MY_LIBRARY_NAME).so

### export dependency on the library
LOCAL_EXPORT_LDLIBS := -L$(LOCAL_PATH)/libs/$(TARGET_ARCH_ABI)/
LOCAL_EXPORT_LDLIBS += -l$(MY_LIBRARY_NAME)

include $(PREBUILT_SHARED_LIBRARY)

这是假设预构建的库位于这样的目录结构中

+ SharedProjectFolderName
+--- Android.mk
+--- include/
+-+- libs/$(TARGET_ARCH_ABI)/
  |- libshared_library_name.so

如果你不是为多个 ABI 构建,我想你可以忽略这一点

项目的 Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := my_jni_module

## source files here, etc...

### define dependency on the other library
LOCAL_SHARED_LIBRARIES := my_module_name

include $(BUILD_SHARED_LIBRARY)

$(call import-add-path,$(LOCAL_PATH)/path/to/myLibraries/)
$(call import-module,SharedProjectFolderName)
$(call import-module,AnotherSharedProject)

我建议您将所有共享库放在一个文件夹中。当您说 $(call import-module,SharedProjectFolderName) 时,它会沿着您告诉它的搜索路径查找包含 Android.mk 的文件夹 (import-add-path)

顺便说一句,您可能不应该指定LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib。它应该自己从 NDK 中找到合适的库。添加更多链接器路径可能会混淆它。正确的方法是将链接器路径作为标志从子模块中导出。

另外,您可以使用ndk-build V=1 来获取大量关于它为什么找不到路径等的信息

【讨论】:

  • 我正在尝试您的解决方案,但出现错误:“在导入路径中找不到带有标签 'AnalyzerPrebuild' 的模块”。您能否提一下 SharedProjectFolderName 相对于主项目的位置?
  • 您可以使用额外的$(call import-add-path,....) 指令添加多个路径。您的所有模块都必须能够以这种方式找到
【解决方案3】:

-L 选项为链接器提供了一个在其中查找库的目录路径。 -l 选项为链接器提供要链接的库文件名。库文件名必须以“lib”开头。您的库应命名为 libsupport_lib1.so 和 libsupport_lib2.so。如果你这样做,那么这可能是你应该做的(替换尝试#1):

LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib -llog -lsupport_lib1 -lsupport_lib2
LOCAL_LDLIBS += -L$(LOCAL_PATH)/lib

链接器将为您使用 -l 指定的库名称添加前缀“lib”,并使用“.so”作为后缀。 (为什么你有 -L$(SYSROOT)/../usr/lib?)

我相信尝试 #1 和 #2 失败是因为您没有将库链接到可执行文件中 - -l 选项中没有提到它们。顺便说一句,您可以自己验证这一点。解压 .apk 文件并查看 lib 目录和子目录。你的 .so 文件在里面吗?

查看错误:

but then... dlopen failed: Could not locate symbol func_that_exists_in_libsupport_lib.so referenced by libnative_lib.so

你能提供完整的信息吗? dlopen() 将库加载并链接到正在运行的进程中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-06-19
    • 1970-01-01
    • 2016-06-06
    • 2012-02-24
    • 1970-01-01
    • 2018-02-20
    • 2017-04-08
    • 1970-01-01
    相关资源
    最近更新 更多