web-dev-qa-db-de.com

Android NDK/JNI: Erstellen einer gemeinsam genutzten Bibliothek, die von anderen gemeinsam genutzten Bibliotheken abhängig ist

Ich schreibe eine Android-App, die JNI-Aufrufe in eine gemeinsam genutzte Bibliothek mit dem NDK ausführen möchte. Der Trick ist, dass diese gemeinsam genutzte Bibliothek Funktionen aus anderen gemeinsam genutzten Bibliotheken aufruft. Die anderen gemeinsam genutzten Bibliotheken sind C-Bibliotheken, die an anderer Stelle kompiliert wurden. 

Folgendes habe ich ausprobiert:

Meine Umgebung: Ich arbeite in Eclipse. Ich habe native Unterstützung hinzugefügt und habe eine Jni-Bibliothek. In dieser Bibliothek habe ich meinen Code und ein Verzeichnis\lib, in das ich meine anderen .so-Dateien kopiert habe. 

Versuch # 1 Android.mk: Sagen Sie einfach, wo sich die Bibliotheken befinden

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)

Dies funktioniert gut, aber wenn ich versuche zu laufen, erhalte ich Fehler, die darauf hinweisen, dass dlopen (libnative_lib) fehlgeschlagen ist, weil libsupport_lib1 nicht geladen werden konnte.

Als ich hierher kam, habe ich folgendes gefunden:

Kann eine gemeinsam genutzte Bibliothek eine andere gemeinsam genutzte Bibliothek aufrufen?

das besagte, dass ich Load Library für alle notwendigen Bibliotheken aufrufen musste. Großartig!

Versuch # 2 Zuerst jede Bibliothek öffnen

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

Auch dies baut sich gut auf, aber beim Ausführen bekomme ich einen neuen Fehler:

libsupport_lib1 konnte nicht geladen werden. findLibrary hat null zurückgegeben.

Jetzt kommen wir irgendwo hin. Die Bibliotheken dürfen nicht in das Ziel geladen werden. 

Versuch Nr. 3 Kopieren von .so-Dateien in project/libs/armeabi

Funktionierte nicht Beim Erstellen von Eclipse wurden die Dateien gelöscht, die ich dort abgelegt habe.

Versuch # 4 Erstellen eines neuen Moduls für jede Bibliothek

Also habe ich das gefunden: 

Android NDK: Verknüpfung mithilfe einer vorkompilierten statischen Bibliothek

Es geht um statische Bibliotheken, aber vielleicht habe ich ein ähnliches Problem. Das Wichtigste ist, dass ich für jede Bibliothek ein Modul deklarieren muss. So sieht mein neues Android.mk so aus:

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)

Das baut! Noch besser, armeabi hat die sos jetzt! Sogar BETTER Ich bekomme die folgenden Meldungen, wenn ich versuche, es auszuführen (und sagt mir, dass support_lib1 und 2 von LoadLibrary geöffnet wurden:

Versuch, lib /data/app-lib/com.example.tst/libsupport_lib1.soadded gemeinsam genutzte lib /data/app-lib/com.example.tst/libsupport_lib1.sono zu laden. JNI_OnLoad gefunden in/data/app-lib/com.example.tst/libsupport_lib1.so, wobei Init übersprungen wird

aber dann ... dlopen fehlgeschlagen: Symbol func_that_exists_in_libsupport_lib.so konnte nicht gefunden werden, auf das von libnative_lib.so verwiesen wird

Edit: Versuch 5: PREBUILT_SHARED_LIBRARY verwenden

Also habe ich folgendes gefunden: Wie kann ich die vorgefertigte gemeinsam genutzte Bibliothek mit dem Android NDK-Projekt verknüpfen?

das scheint genau das zu sein, was ich frage. Ihre Antwort scheint zu sein: 'use_build_shared_library' nicht verwenden, sondern 'PREBUILT_SHARED_LIBRARY' verwenden

Okay, lass es uns versuchen.

 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)

Build ... schlägt fehl! Der Build beschwert sich jetzt über fehlende Symbole. 

Edit: Versuch 6: Flatt alles

Also bin ich auf die Dokumentation zu den Vorkörpern im NDK zurückgekehrt. Es sagt: 

Jede vorgefertigte Bibliothek muss als einziges unabhängiges Modul für das Build-System deklariert werden. Hier ist ein triviales Beispiel, bei dem wir davon ausgehen, dass sich die Datei "libfoo.so" im selben Verzeichnis befindet wie das unten angegebene Android.mk:

LOCAL_PATH := $(call my-dir)

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

Beachten Sie, dass Sie zur Deklaration eines solchen Moduls wirklich nur Folgendes benötigen:

Geben Sie dem Modul einen Namen (hier 'foo-prebuilt'). Dies muss nicht dem Namen der vorgefertigten Bibliothek selbst entsprechen.

Weisen Sie LOCAL_SRC_FILES den Pfad zu der von Ihnen bereitgestellten vorgefertigten Bibliothek zu. Wie üblich ist der Pfad relativ zu Ihrem LOCAL_PATH.

Fügen Sie PREBUILT_SHARED_LIBRARY anstelle von BUILD_SHARED_LIBRARY ein, wenn Sie eine gemeinsam genutzte Bibliothek bereitstellen. Für statische verwenden Sie PREBUILT_STATIC_LIBRARY . Ein vorgefertigtes Modul erstellt nichts. Eine Kopie Ihrer vorgefertigten gemeinsam genutzten Bibliothek wird jedoch in $ PROJECT/obj/local kopiert, und eine andere wird in $ PROJECT/libs/kopiert.

Versuchen wir also, alles zu reduzieren, um das triviale Beispiel zu finden. Ich habe meine Bibliotheken aus ihrem gemütlichen/lib-Ordner kopiert und in das jni-root gestellt. Ich habe dann folgendes gemacht:

 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)

und ... derselbe Fehler. Außerdem sehe ich definitiv KEINE Bibliotheksdateien, die nach $ PROJECT/obj/local kopiert werden.

sooooo .... jetzt was?

16
djc6535

Ihr Problem liegt in der Namenskonvention. NDK und Android bestehen darauf, dass die Namen der gemeinsam genutzten Bibliothek immer mit lib beginnen. Andernfalls werden die Bibliotheken nicht ordnungsgemäß verknüpft und nicht ordnungsgemäß in den Ordner libs/armeabi kopiert und nicht auf dem Gerät installiert (ordnungsgemäß in das Verzeichnis /data/data/package/lib kopiert).

Wenn Sie support_lib1.so in libsupport_1.so und support_lib2.so in libsupport_2.so umbenennen und diese beiden Dateien im jni/lib-Verzeichnis ablegen, funktioniert Ihre Attempt # 5 mit kleineren Änderungen:

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)

Übrigens, ich glaube nicht, dass Sie diese -L$(SYSROOT)/../usr/lib brauchen.

PSVergessen Sie nicht, auch die Java-Seite zu aktualisieren:

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

Ich bin mir nicht sicher, ob Sie genau hier sind, aber ich weiß, was ich über diese Art von Dingen weiß.

  1. Machen Sie jede vorgefertigte Bibliothek zu einem eigenen Makefile. Mehrere Ziele in Android.mk werden neugierig. Traurig.
  2. Jede Make-Datei mit $(call import-add-path) und $(call import-module) einschließen
  3. Exportieren Sie so viel wie möglich aus den make-Dateien der vorgefertigten Dateien, indem Sie die LOCAL_EXPORT_-Variablenfamilie verwenden.

Vorgefertigte gemeinsam genutzte Bibliothek 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)

Dies setzt voraus, dass die vorgefertigten Bibliotheken in einer solchen Struktur leben

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

Wenn Sie nicht für mehrere ABI bauen, können Sie dieses Bit vielleicht weglassen

Das Projekt 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)

Ich empfehle, dass Sie alle gemeinsam genutzten Bibliotheken in einem Ordner ablegen. Wenn Sie $(call import-module,SharedProjectFolderName) sagen, sucht er nach einem Ordner, der einen Android.mk enthält, entlang des Suchpfads, den Sie ihm mitgeteilt haben (import-add-path).

Übrigens sollten Sie LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib wahrscheinlich nicht angeben. Es sollte die richtigen Bibliotheken von NDK selbst finden. Durch das Hinzufügen weiterer Linkerpfade wird dies wahrscheinlich verwirrt. Der richtige Weg ist, die Linkerpfade als Flags aus den Untermodulen zu exportieren.

Außerdem können Sie ndk-build V=1 verwenden, um eine Unmenge von Informationen darüber zu erhalten, warum es keine Pfade finden kann

4
yano

Die Option -L gibt dem Linker einen Verzeichnispfad, in dem nach Bibliotheken gesucht werden soll. Die Option -l gibt dem Linker einen Bibliotheksdateinamen, in den verlinkt werden soll. Bibliotheksdateinamen müssen mit "lib" beginnen. Ihre Bibliotheken sollten libsupport_lib1.so und libsupport_lib2.so heißen. Wenn Sie das tun, sollten Sie wahrscheinlich Folgendes tun (Versuch Nr. 1 ersetzen):

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

Der Linker fügt den von Ihnen angegebenen Bibliotheksnamen mit -l mit "lib" voran und fügt ihn mit ".so" hinzu. (Warum haben Sie -L $ (SYSROOT) /../ usr/lib?)

Ich glaube, dass die Versuche Nr. 1 und Nr. 2 fehlgeschlagen sind, weil Sie Ihre Bibliotheken nicht mit Ihrer ausführbaren Datei verknüpft haben. Sie werden nicht in einer Option -l erwähnt. Sie können dies übrigens selbst überprüfen. Entpacken Sie die .apk-Datei und suchen Sie im lib-Verzeichnis und den Unterverzeichnissen. Sind Ihre .so-Dateien dort?

Betrachtet man den Fehler:

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

Können Sie die gesamte Nachricht angeben? dlopen () lädt Bibliotheken und verknüpft sie mit dem laufenden Prozess.

0
Sam