Android NDK/JNI: создание общей библиотеки, которая зависит от других разделяемых библиотек

Я пишу приложение для 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.

Придя сюда, я нашел это:

Может ли общая библиотека вызывать другую разделяемую библиотеку?

в котором говорилось, что мне нужно вызвать библиотеку загрузки для всех необходимых библиотек. Отлично!

Попытка # 2 Открытие каждой библиотеки сначала

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

Опять же, это просто отлично, но когда я запускаю, я получаю новую ошибку:

не удалось загрузить libsupport_lib1. findLibrary возвращается null.

Теперь мы куда-то добираемся. Он не должен загружать библиотеки в цель.

Попытка # 3 Копирование .so файлов в проект /libs/armeabi

Не работает. Когда Eclipse создает, он удаляет файлы, которые я туда запустил.

Попытка # 4 Создание нового модуля для каждой библиотеки

Итак, я нашел это:

Android NDK: ссылка с использованием предварительно скомпилированной статической библиотеки

Это о статических библиотеках, но, возможно, у меня такая же проблема. Суть в том, что мне нужно объявить модуль для каждой библиотеки. Итак, мой новый 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)

Это строит! Еще лучше, теперь армеаби имеет sos! Даже ЛУЧШЕ Я получаю следующие сообщения, когда я пытаюсь запустить его (говоря мне, что support_lib1 и 2 были открыты LoadLibrary:

Попытка загрузить lib/data/app-lib/com.example.tst/libsupport_lib1.so добавлен общий файл lib/data/app-lib/com.example.tst/libsupport_lib1.so нет JNI_OnLoad найдено в /data/app -lib/com.example.tst/libsupport_lib1.so, пропущен init

но затем... dlopen не удалось: не удалось найти символ func_that_exists_in_libsupport_lib.so, на который ссылается libnative_lib.so

Изменить: Попытка 5: Использовать PREBUILT_SHARED_LIBRARY

Итак, я нашел это: Как я могу связать предварительно созданную общую библиотеку с проектом NDK для Android?

который, кажется, именно то, что я прошу. Их ответ кажется "не использовать" 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-root. Затем я сделал следующее:

 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.

sooooo.... теперь что?

Ответы

Ответ 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");
}

Ответ 2

Не уверен, что это именно то, где вы находитесь, но вот что я знаю об этих вещах.

  • Сделайте каждую предварительно созданную библиотеку своей отдельной Makefile. Несколько целей в Android.mk, как правило, становятся неуклюжими. Сад.
  • Включите каждый файл make с помощью $(call import-add-path) и $(call import-module)
  • Экспортируйте как можно больше из предварительно созданных файлов make, используя семейство переменных LOCAL_EXPORT_.

Предварительная общая библиотека 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)

Это предполагает, что предварительно созданные библиотеки хранятся в структуре dir, подобной этой

+ 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, чтобы получить тонну информации о том, почему она не может найти пути и т.д.

Ответ 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() загружает и связывает библиотеки в текущий процесс.