web-dev-qa-db-fra.com

Android NDK/JNI: construction d'une bibliothèque partagée qui dépend d'autres bibliothèques partagées

J'écris une application Android qui souhaite passer des appels JNI dans une bibliothèque partagée intégrée à l'aide du NDK. Le truc, c'est que cette bibliothèque partagée appelle des fonctions fournies par AUTRES bibliothèques partagées. Les autres bibliothèques partagées sont des bibliothèques C qui ont été compilées ailleurs. 

Voici ce que j'ai essayé:

Mon environnement: Je travaille dans Eclipse. J'ai ajouté un support natif et une bibliothèque jni. Dans cette bibliothèque, j'ai mon code et un répertoire\lib où j'ai copié mes autres fichiers .so. 

Attempt # 1 Android.mk: Il suffit de lui dire où se trouvent les bibliothèques

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)

Cela fonctionne très bien, mais lorsque j'essaie de l'exécuter, des erreurs indiquent que dlopen (libnative_lib) a échoué car il n'a pas pu charger libsupport_lib1.

En venant ici, j'ai trouvé ceci:

Une bibliothèque partagée peut-elle appeler une autre bibliothèque partagée?

qui a dit que je devais appeler la bibliothèque de chargement sur toutes les bibliothèques nécessaires. Génial!

Tentative # 2 Ouverture de chaque bibliothèque en premier

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

Encore une fois, cela fonctionne très bien, mais lorsque je cours, je reçois une nouvelle erreur:

impossible de charger libsupport_lib1. findLibrary a renvoyé la valeur null.

Maintenant, nous allons quelque part. Il ne faut pas charger les bibliothèques sur la cible. 

Attempt # 3 Copie de fichiers .so dans project/libs/armeabi

N'a pas fonctionné Lorsque Eclipse a été créé, il a supprimé les fichiers que j'ai déposés.

Attempt # 4 Création d'un nouveau module pour chaque bibliothèque

Alors j'ai trouvé ceci: 

Android NDK: lien utilisant une bibliothèque statique précompilée

Il s’agit de bibliothèques statiques, mais j’ai peut-être un problème similaire. Le Gist est que je dois déclarer un module pour chaque bibliothèque. Donc, mon nouveau Android.mk ressemble à ceci:

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)

Cela construit! Mieux encore, armeabi a le sos maintenant! Même MIEUX, je reçois les messages suivants lorsque je tente de l'exécuter (me disant que support_lib1 et 2 ont été ouverts par LoadLibrary:

Essayez de charger lib /data/app-lib/com.example.tst/libsupport_lib1.soadd ajouté lib /data/app-lib/com.example.tst/libsupport_lib1.sono.JNI_OnLoad trouvé dans/data/app-lib/com.example.tst/libsupport_lib1.so, ignorant init

mais alors ... dlopen a échoué: impossible de localiser le symbole func_that_exists_in_libsupport_lib.so référencé par libnative_lib.so.

Edit: Essai 5: Utilisez PREBUILT_SHARED_LIBRARY

J'ai donc trouvé ceci: Comment puis-je associer une bibliothèque partagée préconfigurée à un projet Android NDK?

ce qui semble être exactement ce que je demande. Leur réponse semble être 'n'utilisez pas' build_shared_library 'mais' utilisez plutôt PREBUILT_SHARED_LIBRARY

Ok, essayons.

 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)

Construire ... échoue! La construction se plaint maintenant des symboles manquants. 

Edit: Essai 6: Aplatissez tout

Je suis donc retourné à la documentation de pré-compilation dans le NDK. Ça dit: 

Chaque bibliothèque pré-construite doit être déclarée comme un seul module indépendant du système de construction. Voici un exemple trivial où nous supposons que le fichier "libfoo.so" est situé dans le même répertoire que le fichier Android.mk ci-dessous:

LOCAL_PATH := $(call my-dir)

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

Notez que, pour déclarer un tel module, vous n’avez vraiment besoin que des éléments suivants:

Donnez un nom au module (ici 'foo-prebuilt'). Cela ne doit pas nécessairement correspondre au nom de la bibliothèque prédéfinie elle-même.

Attribuez à LOCAL_SRC_FILES le chemin d'accès à la bibliothèque prédéfinie que vous fournissez. Comme d'habitude, le chemin est relatif à votre LOCAL_PATH.

Incluez PREBUILT_SHARED_LIBRARY au lieu de BUILD_SHARED_LIBRARY si vous fournissez une bibliothèque partagée. Pour les versions statiques, utilisez PREBUILT_STATIC_LIBRARY . Un module pré-construit ne construit rien. Toutefois, une copie de votre bibliothèque partagée prédéfinie sera copiée dans $ PROJECT/obj/local et une autre sera copiée et supprimée dans $ PROJECT/libs /.

Essayons donc de tout aplatir pour correspondre à l'exemple trivial. J'ai copié mes bibliothèques hors de leur dossier cosy/lib et les ai placées dans la racine jni. J'ai ensuite fait ceci:

 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)

et ... même erreur. De plus, je ne vois absolument PAS que les fichiers de la bibliothèque soient copiés dans $ PROJECT/obj/local.

sooooo .... maintenant quoi?

16
djc6535

Votre problème est avec la convention de nommage. NDK et Android insistent pour que les noms de bibliothèques partagées commencent toujours par lib . Sinon, les bibliothèques ne seront pas liées correctement, ni copiées correctement dans le dossier libs/armeabi, ni installées sur le périphérique (copiées correctement dans le répertoire /data/data/package/lib.

Si vous renommez support_lib1.so en libsupport_1.so et support_lib2.so en libsupport_2.so et mettez ces deux fichiers dans le répertoire jni/lib, votre Attempt # 5 fonctionnera avec des modifications mineures:

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)

BTW, je ne pense pas que vous ayez besoin de cette -L$(SYSROOT)/../usr/lib.

PSN'oubliez pas de mettre à jour le côté Java également:

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

Vous ne savez pas exactement où vous en êtes, mais voici ce que je sais de ce genre de choses.

  1. Attribuez à chaque bibliothèque pré-construite son propre fichier Makefile. Dans Android.mk, les cibles multiples ont tendance à s’égarer. Triste.
  2. Inclure chaque fichier de marque en utilisant $(call import-add-path) et $(call import-module)
  3. Exportez autant que vous le pouvez à partir des fichiers make prédéfinis, à l'aide de la famille de variables LOCAL_EXPORT_.

Bibliothèque partagée prédéfinie 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)

Ceci suppose que les bibliothèques pré-construites vivent dans une structure de répertoire comme celle-ci.

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

Si vous ne construisez pas pour plusieurs ABI, je suppose que vous pouvez omettre cela

Le projet 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)

Je vous recommande de mettre toutes les bibliothèques partagées dans un seul dossier. Lorsque vous dites $(call import-module,SharedProjectFolderName), il recherche un dossier contenant un Android.mk dans le chemin de recherche que vous lui avez indiqué (import-add-path).

En passant, vous ne devriez probablement pas spécifier LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib. Il devrait trouver les bonnes bibliothèques à partir de NDK par lui-même. Ajouter plus de chemins de l'éditeur de liens le confondra probablement. La méthode appropriée consiste à exporter les chemins de l’éditeur de liens sous forme de drapeaux à partir des sous-modules.

AUSSI, vous pouvez utiliser ndk-build V=1 pour obtenir une tonne d'informations sur les raisons pour lesquelles il ne trouve pas les chemins, etc.

4
yano

L'option -L donne à l'éditeur de liens un chemin de répertoire dans lequel rechercher des bibliothèques. L'option -l donne à l'éditeur de liens un nom de fichier de bibliothèque à associer. Les noms de fichier de bibliothèque doivent commencer par "lib". Vos bibliothèques doivent s'appeler libsupport_lib1.so et libsupport_lib2.so. Si vous faites cela, c'est probablement ce que vous devriez faire (en remplaçant la tentative n ° 1):

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

L'éditeur de liens préfixera le nom de la bibliothèque que vous spécifiez à l'aide de -l avec "lib" et suffixera avec ".so". (Pourquoi avez-vous $ (SYSROOT) /../ usr/lib?)

Je pense que les tentatives n ° 1 et n ° 2 ont échoué car vous n’avez pas lié vos bibliothèques à votre exécutable - elles ne sont pas mentionnées dans une option -l. Au fait, vous pouvez le vérifier vous-même. Décompressez le fichier .apk et regardez dans le répertoire lib et les sous-répertoires. Est-ce que vos fichiers .so sont là?

En regardant l'erreur:

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

Pouvez-vous fournir le message entier? dlopen () charge et relie les bibliothèques au processus en cours.

0
Sam