Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android NDK/JNI: Building a shared library that depends on other shared libraries

I am writing an android app that wants to make JNI calls into a shared library built in using the NDK. The trick is this shared library calls functions provided by OTHER shared libraries. The other shared libraries are C libraries that have been compiled elsewhere.

Here's what I've tried:

My Environment: I'm working in Eclipse. I've added native support and have a jni library. In that library I have my code and a \lib directory where I have copied my other .so files.

Attempt #1 Android.mk: Just telling it where the libs are

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)

This builds just fine, but when I try to run I get errors indicating that dlopen(libnative_lib) failed because it couldn't load libsupport_lib1.

Coming here I found this:

Can shared library call another shared library?

which said that I needed to call load library on all necessary libraries. Great!

Attempt #2 Opening each library first

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

Again, this builds just fine, however when I run I get a new error:

couldn't load libsupport_lib1. findLibrary returned null.

Now we're getting somewhere. It must not be loading the libraries over to the target.

Attempt #3 Copying .so files into project/libs/armeabi

Didn't work. When Eclipse builds it deleted the files I dropped in there.

Attempt #4 Creating a new module for each library

So then I found this:

Android NDK: Link using a pre-compiled static library

It's about static libraries, but maybe I am having a similar problem. The gist is that I need to declare a module for each library. So my new Android.mk looks like this:

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)

This builds! Even better, armeabi has the sos now! Even BETTER I get the following messages when I try to run it (telling me that support_lib1 and 2 were opened by LoadLibrary:

Trying to load lib /data/app-lib/com.example.tst/libsupport_lib1.so added shared lib /data/app-lib/com.example.tst/libsupport_lib1.so no JNI_OnLoad found in /data/app-lib/com.example.tst/libsupport_lib1.so, skipping init

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

Edit: Attempt 5: Use PREBUILT_SHARED_LIBRARY

So I found this: How can i Link prebuilt shared Library to Android NDK project?

which seems to be exactly what I'm asking. Their answer seems to be 'don't use 'build_shared_library' but instead 'use PREBUILT_SHARED_LIBRARY

Okay, let's try.

 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... fails! The build complains about missing symbols now.

Edit: Attempt 6: Flatten everything

So I went back to the prebuilts documentation in the NDK. It says:

Each prebuilt library must be declared as a single independent module to the build system. Here is a trivial example where we assume that the file "libfoo.so" is located in the same directory than the Android.mk below:

LOCAL_PATH := $(call my-dir)

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

Notice that, to declare such a module, you really only need the following:

Give the module a name (here 'foo-prebuilt'). This does not need to correspond to the name of the prebuilt library itself.

Assign to LOCAL_SRC_FILES the path to the prebuilt library you are providing. As usual, the path is relative to your LOCAL_PATH.

Include PREBUILT_SHARED_LIBRARY, instead of BUILD_SHARED_LIBRARY, if you are providing a shared, library. For static ones, use PREBUILT_STATIC_LIBRARY. A prebuilt module does not build anything. However, a copy of your prebuilt shared library will be copied into $PROJECT/obj/local, and another will be copied and stripped into $PROJECT/libs/.

So let's try flattening everything out to match the trivial example. I copied my libraries out of their cozy /lib folder and put them in the jni root. I then did this:

 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)

and... same error. Moreover I'm most definitely NOT seeing library files getting copied to $PROJECT/obj/local.

sooooo.... now what?

like image 232
djc6535 Avatar asked Feb 21 '14 00:02

djc6535


People also ask

What is NDK and JNI?

JNI is just the way that Java handles calling into native/C++ code, and calling back into Java from there. It has nothing to say about Android - it is a Java language feature. The Android NDK is a way to write Android applications using code called by JNI.

What is NDK build?

The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input.

How does JNI work on Android?

It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++). JNI is vendor-neutral, has support for loading code from dynamic shared libraries, and while cumbersome at times is reasonably efficient.


2 Answers

Your problem is with the naming convention. NDK and Android insist on the shared library names to always begin with lib. Otherwise, the libraries will not be linked properly, and not copied to the libs/armeabi folder properly, and not installed on the device (copied to /data/data/package/lib directory properly.

If you rename support_lib1.so to libsupport_1.so and support_lib2.so to libsupport_2.so, and put these two files in jni/lib directory, then your Attempt #5 will work with minor change:

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, I don't think you need this -L$(SYSROOT)/../usr/lib.

PS Don't forget to update the Java side, too:

static {
    System.loadLibrary("support_lib1");
    System.loadLibrary("support_lib2");
    System.loadLibrary("native_lib");
}
like image 105
Alex Cohn Avatar answered Sep 20 '22 14:09

Alex Cohn


Not sure if this is exactly where you are at, but here's what I know about these sorts of things.

  1. Make each prebuilt libary its own separate Makefile. Multiple targets in Android.mk tends to get wonky. Sad.
  2. Include each make file using $(call import-add-path) and $(call import-module)
  3. Export as much as you can from the prebuilt's make files, using the LOCAL_EXPORT_ family of variables.

Prebuilt Shared Library 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)

This is assuming that the prebuilt libaries live in a dir structure like this

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

If you are not building for multiple ABI, I guess you can leave that bit out

The Project's 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)

I recommend you put all shared libraries in one folder. When you say $(call import-module,SharedProjectFolderName) it looks for a folder containing an Android.mk along the search path you told it (import-add-path)

By the way, you probably shouldn't specify LOCAL_LDLIBS := -L$(SYSROOT)/../usr/lib. It should be finding the proper libs from NDK by itself. Adding more linker paths will probably confuse it. The proper way is to export the linker paths as flags from the sub-modules.

ALSO, you can use ndk-build V=1 to get a ton of info on why it can't find paths, etc

like image 45
yano Avatar answered Sep 20 '22 14:09

yano