JNI/NDK library question: Trouble building & linking shared libraries.

262 views
Skip to first unread message

Dave Chandler

unread,
Feb 21, 2014, 11:37:02 AM2/21/14
to andro...@googlegroups.com
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:



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: 


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:

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/<abi>.


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?

Tim Mensch

unread,
Feb 24, 2014, 11:56:32 PM2/24/14
to andro...@googlegroups.com
On 2/21/2014 9:37 AM, Dave Chandler wrote:
and... same error.  Moreover I'm most definitely NOT seeing library files getting copied to $PROJECT/obj/local.

sooooo.... now what?

The last approach looks just like the one that I've used successfully. It has explicit build tasks to copy those libraries.

Make sure it's finding the support libraries. Do you see an "Install: support_lib1.so => libs/armeabi/support_lib1.so" step for each library every time you ndk-build? It should do that every time.

Do an "ndk-build clean" to make sure bogus .so files (possibly generated by the incorrect Attempt #4) aren't still hanging around.

The source path I'm using is an absolute path, not a relative path. Not sure if that matters.

And if that doesn't help, then you would need to post more specific build output.

The last approach does work. I can guarantee it; I've been using it for months now. I'm on r9c at this point, though I first tried that approach with r9. I don't see any typos in the code in your email, but that doesn't mean there aren't any.

Good luck,

Tim

J Decker

unread,
Feb 25, 2014, 12:55:01 AM2/25/14
to andro...@googlegroups.com
I don't know if this will help or not; but here's an approach that
works, with reasoning that built it.

Android supports LD_LIBRARY_PATH, but doesn't pay attention to that if
it's set after your application starts (even if it's before the first
reference to libdl.so). You do get all files call lib*.so in
lib/armeabi (for instance); but auto loading will not load.

If you dlopen a file that doesn't already have its required libraries
to load, it will fail; and if you were clever, you could parse the
dlerr result and see what library it needed, and then recurse to load
that library, and reload the current; but if you reload the same
library, you get an error like 'already failed loading'... so it keeps
track of libraries it loaded, but not that it failed on library X
which in the meantime was loaded.

You really just need to setup a file that has the list of libraries to
load, and while( fgets( buf, 256, file ) ) { dlopen( buf, 0 ); /*...*/
}

You need to link the libraries so that they do not have the full path
(cmake mingw makefiles will by default generate a full path to the
library, expecting it to be stripped later; which maybe if I knew what
that step was supposed to be; wouldn't matter).... so it should always
be linked like...

gcc -L/path/to/lib -l<simple_libraryname> ...
> --
> You received this message because you are subscribed to the Google Groups
> "android-ndk" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to android-ndk...@googlegroups.com.
> To post to this group, send email to andro...@googlegroups.com.
> Visit this group at http://groups.google.com/group/android-ndk.
> For more options, visit https://groups.google.com/groups/opt_out.

J Decker

unread,
Feb 25, 2014, 1:03:14 AM2/25/14
to andro...@googlegroups.com
Sorry

On Mon, Feb 24, 2014 at 9:55 PM, J Decker <d3c...@gmail.com> wrote:
> I don't know if this will help or not; but here's an approach that
> works, with reasoning that built it.
>
> Android supports LD_LIBRARY_PATH, but doesn't pay attention to that if
> it's set after your application starts (even if it's before the first
> reference to libdl.so). You do get all files call lib*.so in
> lib/armeabi (for instance); but auto loading will not load.
>
> If you dlopen a file that doesn't already have its required libraries
> to load, it will fail; and if you were clever, you could parse the
> dlerr result and see what library it needed, and then recurse to load
> that library, and reload the current; but if you reload the same
> library, you get an error like 'already failed loading'... so it keeps
> track of libraries it loaded, but not that it failed on library X
> which in the meantime was loaded.
>
> You really just need to setup a file that has the list of libraries to
> load, and while( fgets( buf, 256, file ) ) { dlopen( buf, 0 ); /*...*/
> }
>
> You need to link the libraries so that they do not have the full path
> (cmake mingw makefiles will by default generate a full path to the
> library, expecting it to be stripped later; which maybe if I knew what
> that step was supposed to be; wouldn't matter).... so it should always
> be linked like...
>
> gcc -L/path/to/lib -l<simple_libraryname> ...
>

You will need to know where your library is... you can get that by
reading /proc/self/maps

https://code.google.com/p/c-system-abstraction-component-gui/wiki/AndroidStartupCode
Reply all
Reply to author
Forward
0 new messages