Android NDK: how to link multiple 3rd party librar

2019-02-07 14:53发布

问题:

Let's say we're building a shared library A that needs to link to 2 external static libs B and C. All you've got are libB.a and libC.a, along with their header files.

Here's a simplified Android.mk for libA:

LOCAL_LDLIBS := ../external/libB.a ../external/libC.a

include $(BUILD_SHARED_LIBRARY)

AFAIK, the way linking works for shared libraries is:

  1. grab all object files of B and C
  2. strip out object files that A doesn't reference
  3. resolve references in B and C

This gives link errors because B and C call each other, specifically they call functions that got stripped out in step 2 because A didn't call them.

If we built the static libs ourselves, then it's simply a matter of replacing LOCAL_STATIC_LIBRARIES with LOCAL_WHOLE_STATIC_LIBRARIES, which prevents code stripping (at the expense of code size). Under the hood, it passes --whole-archive to the linker.

Since we didn't build B and C (and don't even have the source to rebuild them), what are the options?

  1. manually reference the missing functions from A, so that they don't get stripped
  2. figure out how to pass --whole-archive to the linker for the external static libraries
  3. use the PREBUILT_STATIC_LIBRARY (seen it mentioned, but never used it, and the according to the docs it doesn't sound applicable in this case)
  4. build an executable instead of a shared library (which won't strip code the same way)
  5. move/rename external libs to trick the NDK build system into thinking they're mine, so that I can add them to LOCAL_WHOLE_STATIC_LIBRARIES.

I've gone with option 1 because it's the first thing that worked, but obviously it's not great. I'm asking whether there's a better solution.

The answer to this question ( Linking issue when prebuilt static and shared libraries with the Android NDK ) made me wonder if I need to re-evaluate my build setup (shared library linking to external static library). I'm unable to comment there, so I asked my own question here.

回答1:

The answer can be found in How to deal with recursive dependencies between static libraries using the binutils linker?.

LOCAL_LDLIBS := -L ../external/ -lB -lC -lB

I took the two-libs NDK sample, and made minimal change to demonstrate the technique on GitHub.

Update (2017): since 2012, the rules of NDK became more strict, and now it will complain that LOCAL_LDLIBS contains non-system libraries:

Android NDK: WARNING:Android.module: non-system libraries in linker flags: -la -lb
Android NDK: This is likely to result in incorrect builds. Try using LOCAL_STATIC_LIBRARIES
Android NDK: or LOCAL_SHARED_LIBRARIES instead to list the library dependencies of the
Android NDK: current module

This is just a warning, so you can ignore it.

Or add space after -l to outsmart the NDK protection:

LOCAL_LDLIBS := -L ../external/ -l B -l C -l B

Alternatively, you can use

LOCAL_LDLIBS += -L ../external -Wl,--start-group -l B -l C -Wl,--end-group

If the libraries that are involved are not prebuilt, you don't need to guess their location (which may be especially tricky when using Android Studio NDK integration). Use

LOCAL_LDLIBS := -L $(TARGET_OUT) …

There exists an alternative approach, which does not use 'recursive' linking. But it involves iterations. First, try to build your shared library the usual way. When this fails with unresolved symbols, copy all these symbols to clipboard, and paste them your Android.mk. Let's say these symbols are extBa, extBb, and extBc (in the above scenario, I believe that some object of libC does not find these symbols that are defined somewhere in libB, that's why the link fails). What you need now, add

LOCAL_LDFLAGS += -Wl,-u'extBa' -Wl,-u'extBb' -Wl,-u'extBc'

You can make the next step, and have this all bundled with libC:

LOCAL_EXPORT_LDFLAGS += -Wl,-u'extBa' -Wl,-u'extBb' -Wl,-u'extBc'

Now any shared lib that uses libC will not miss these symbols.