2

I'm quite new with NDK + Gradle + CMake integration and I'm trying to understand why linking doesn't export symbols as intended.

I have a static library built by a CMakeLists.txt which is not the main CMakeLists.txt.

The scripts does something like:

# main CMakeLists.txt
add_subdirectory(${LIBS}/foo libs}

add_library(native SHARED native.cpp)
# omitting standard android libraries
target_link_libraries(native foo ${android-lib} ${log-lib})

while CMakeLists.txt inside ${libs}/foo is the following:

# misc configuration of ${SRC}
add_library(foo STATIC ${SRC})

The script works fine, it's able to link libnative.so and I'm able to find the generated libfoo.a. Everything seems fine.

I then try to define a native method in foo.cpp contained in foo library:

extern "C" JNIEXPORT void JNICALL Java_com_mypackage_Controls_onTap(JNIEnv*, jobject, int x, int y) {
  // log something
}

But I'm not able to call the native method defined in foo library. I get an UnsatisfiedLinkError at runtime. If, instead, I move (directly by copying and pasting) the method to native.cpp then everything goes fine.

So basically:

  • Java -> method in native.cpp works
  • Java -> method in native.cpp -> method defined in foo library works
  • Java -> method in foo library doesn't work (UnsatisfiedLinkError)

I tried to inspect the exported functions with nm and it looks like that foo.a correctly exports the native function as I can see

00011060 T Java_com_mypackage_Controls_onTap

But this entry disappears from libnative.so. If, instead, I define the method directly in native.cpp then I can see it correctly with nm also on libnative.so.

In addition calling any method in foo library from native.cpp works as intended so the library is effectively statically linked.

I am not able to understand the reason behind this, the approach should be fine, visibility should be correct as specified by JNIEXPORT macro so I'm really groping in the dark (and Gradle doesn't provide any output of compilation phase so I can't understand what's happening, but the build.ninja file seems correct)

Jack
  • 131,802
  • 30
  • 241
  • 343
  • Have you tried to add `set( CMAKE_VERBOSE_MAKEFILE on )`? From your explanation it is hard to tell what went wrong - either the symbol is stripped from libnative.so, or that linking libfoo.a is skipped altogether. – Alex Cohn Oct 15 '16 at 20:54
  • @AlexCohn linking of libfoo.a is not skipped as I'm able to invoke a method of libfoo from libnative and it correctly links and works. It seems like the symbol is stripped or not exported visibly from outside but I tried to force -fvisibility=default without any success. Unfortunately the generator used is not make so there is no makefile. I can see the invocation command and it seems correct. The so is generated by liking native.o and libfoo.a. I've really no clues.. – Jack Oct 15 '16 at 21:59

1 Answers1

4

This behavior, even if unpleasant, is correct. The linker drops any object "files" (in your case, foo.o) from used static libraries, unless they are "pinned" by one of the objects in the shared lib (in your case, native.o). There are three ways to solve the problem:

  1. compile foo.cpp as part of libnative.so instead of a static lib.

  2. reference Java_com_mypackage_Controls_onTap or any other external symbol from foo.cpp in native.cpp

  3. use SET(native -Wl,--whole-archive foo -Wl,--no-whole-archive) (see https://stackoverflow.com/a/17477559/192373)

Community
  • 1
  • 1
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • Thanks, by keeping with searches to related issued (not involving Android NDK) I reached the same conclusion. While using `--whole-archive` seems like the panacea to solve this issue, I was wondering if there's a way to mark specific methods as "export always", maybe through an `__attribute__` just to keep things sane, not just by blindly exporting any symbol of any object file (since in the end just maybe 10 out of 1000 will be used)? – Jack Oct 16 '16 at 09:15
  • No, there exists no linker option "keep symbols a, b and q even if nobody needs them". But (see *2.* above) it's easy to simulate such option. If you switch from implicit JNI binding to explicit **RegisterNatives()**, you will get this automatically. – Alex Cohn Oct 16 '16 at 19:56