I have a static library, Foo, that is used by a shared library, Bar. Bar is the native shared library loaded by my Android app. Foo contains JNI functions that are only called by Java code and not by any C++ code in Bar. Because of this, those JNI functions get stripped out of the static library (Foo) when the shared library (Bar) is built. I'm currently using a slightly hacky method to prevent that from happening.
So, in this case, is there a way to tell the compiler to not strip the JNI (or any) functions out when linking?
They're not getting stripped, they're getting ignored. When the shared library is linked, the linker is only pulling in the object files with functions that are actually used. (This is how static libs are defined to work.)
I believe passing the "--whole-archive" flag to the linker will cause it to pull in all object files from a static library. You can provide it on the gcc link line with "-Wl,-whole-archive". You need to follow it with "-Wl,-no-whole-archive" after specifying your library, or ld will continue the behavior for any other static libraries it encounters, which is likely not the behavior you want. See also the ld(1) man page on a Linux system.
Another way to accomplish the same thing is to output a single massive .o file instead of a .a file.
EDIT: Simple command-line example, using libz on the desktop:
% echo "int main() { return 0; }" > foo.c
% gcc -o foo /usr/lib/libz.a foo.c
% ls -s foo
12 foo*
% gcc -o foo -Wl,-whole-archive /usr/lib/libz.a -Wl,-no-whole-archive foo.c
% ls -s foo
104 foo*
(You have to use "/usr/lib/libz.a" instead of "-lz" here because the latter finds the shared library /usr/lib/libz.so.)
I haven't used the NDK much, but it looks like adding the flags to LOCAL_LDFLAGS might do the trick.
Let's start with the basic two-libs sample from NDK. Here is its original Android.mk file:
1 # Copyright (C) 2009 The Android Open Source Project
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15
16 # the purpose of this sample is to demonstrate how one can
17 # generate two distinct shared libraries and have them both
18 # uploaded in
19 #
20
21 LOCAL_PATH:= $(call my-dir)
22
23 # first lib, which will be built statically
24 #
25 include $(CLEAR_VARS)
26
27 LOCAL_MODULE := libtwolib-first
28 LOCAL_SRC_FILES := first.c
29
30 include $(BUILD_STATIC_LIBRARY)
31
32 # second lib, which will depend on and include the first one
33 #
34 include $(CLEAR_VARS)
35
36 LOCAL_MODULE := libtwolib-second
37 LOCAL_SRC_FILES := second.c
38
39 LOCAL_STATIC_LIBRARIES := libtwolib-first
40
41 include $(BUILD_SHARED_LIBRARY)
Note that the JNI function is located in second.c
which is not part of the libtwolib-first
static library.
First, let us reproduce your problem. The change is simple:
...
27 LOCAL_MODULE := libtwolib-first
28 LOCAL_SRC_FILES := first.c second.c
...
36 LOCAL_MODULE := libtwolib-second
37 LOCAL_SRC_FILES :=
If you run the modified project, you will get the following error:
E/AndroidRuntime( 4213): FATAL EXCEPTION: main
E/AndroidRuntime( 4213): java.lang.UnsatisfiedLinkError: add
E/AndroidRuntime( 4213): at com.example.twolibs.TwoLibs.add(Native Method)
E/AndroidRuntime( 4213): at com.example.twolibs.TwoLibs.onCreate(TwoLibs.java:39)
E/AndroidRuntime( 4213): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
E/AndroidRuntime( 4213): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
E/AndroidRuntime( 4213): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
E/AndroidRuntime( 4213): at android.app.ActivityThread.access$2300(ActivityThread.java:125)
E/AndroidRuntime( 4213): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
E/AndroidRuntime( 4213): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 4213): at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime( 4213): at android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime( 4213): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 4213): at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime( 4213): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871)
E/AndroidRuntime( 4213): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)
E/AndroidRuntime( 4213): at dalvik.system.NativeStart.main(Native Method)
You explained this problem precisely: the linker "stripped out" the "unused" Java_com_example_twolibs_TwoLibs_add() entry.
Now let us fix this:
39 LOCAL_STATIC_LIBRARIES := libtwolib-first39 LOCAL_WHOLE_STATIC_LIBRARIES := libtwolib-first
And again the sample works!
For those using CMakeLists.txt
, here's how you do it:
target_link_libraries(
# The shared library name,
Bar
# Since Foo is a static library, only the symbols used in Bar are kept. But
# we intend to use other symbols from the java side, hence, we instruct the
# linker to grab the whole library,
-Wl,-whole-archive
Foo
-Wl,-no-whole-archive
# Other libraries needed, potentially including some static libraries that
# will be treated the normal way,
android
${log-lib})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With