Dynamic loading is a technique to load a library into memory at runtime, and execute functions or access variables defined in the library. It allows the app to start without these libraries.
We have seen dynamic loading in almost every recipe of this book. When we call the System.loadLibrary
or System.load
function to load the native libraries, we are using dynamic loading.
Android NDK has provided the dynamic linker library to support dynamic loading in NDK, since Android 1.5. This recipe discusses the dynamic linker library functions.
Readers are expected to know how to create an Android NDK project. You can refer to the Writing a Hello NDK program recipe of Chapter 1, Hello NDK for detailed instructions.
The following steps describe how to create an Android application using the dynamic linking library to load the math library and compute the square root of 2.
DynamicLinker
. Set the package name as cookbook.chapter7.dynamiclinker
. Refer to the Loading native libraries and registering native methods recipe of Chapter 2, Java Native Interface for more detailed instructions.DynamicLinker
project, select Android Tools | Add Native Support.MainActivity.java
under the cookbook.chapter7.dynamiclinker
package. This Java file simply loads the native DynamicLinker
library and calls the native naDLDemo
method.mylog.h
and DynamicLinker.cpp
files under the jni
folder. A part of the code in the OpenSLESDemo.cpp
file is shown in the following code.naDLDemo
loads the libm.so
library,
obtains the address of the sqrt
function and calls the function with input argument 2.0
:
void naDLDemo(JNIEnv* pEnv, jclass clazz) { void *handle; double (*sqrt)(double); const char *error; handle = dlopen("libm.so", RTLD_LAZY); if (!handle) { LOGI(1, "%s ", dlerror()); return; } dlerror(); /* Clear any existing error */ *(void **) (&sqrt) = dlsym(handle, "sqrt"); if ((error = dlerror()) != NULL) { LOGI(1, "%s ", error); return; } LOGI(1, "%f ", (*sqrt)(2.0)); }
Android.mk
file under the jni
folder with the following content:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := DynamicLinker LOCAL_SRC_FILES := DynamicLinker.cpp LOCAL_LDLIBS := -llog -ldl include $(BUILD_SHARED_LIBRARY)
logcat
output:$ adb logcat -v time DynamicLinker:I *:S
A screenshot of the logcat
output is shown as follows:
In order to build with dynamic loading library libdl.so
, we must add the following line to the Android.mk
file:
LOCAL_LDLIBS := -ldl
The following functions are defined in the dlfcn.h
header file by
the Android dynamic linking library:
void* dlopen(const char* filename, int flag); int dlclose(void* handle); const char* dlerror(void); void* dlsym(void* handle, const char* symbol); int dladdr(const void* addr, Dl_info *info);
The dlopen
function loads the library dynamically. The first argument indicates the library name, while the second argument refers to the loading mode, which describes how dlopen
resolves the undefined symbols. When an object file (for example, shared library, executable file, and so on) is loaded, it may contain references to symbols whose addresses are not known until another object file is loaded (such symbols are referred to as undefined symbols). These references need to be resolved before they can be used to access the symbols. The following two modes determine when the resolving happens:
RTLD_NOW
: When the object file is loaded, the undefined symbols are resolved. This means the resolving occurs before the dlopen
function returns. This may be a waste if resolving is performed but the references are never accessed.RTLD_LAZY
: The resolving can be performed after the dlopen
function returns, that is, the undefined symbols are resolved when the code is executed.The following two modes determine the visibility of the symbols in the loaded object. They can be ORed with the previously mentioned two modes:
RTLD_LOCAL
: The symbols will not be available for another objectRTLD_GLOBAL
: The symbols will be available for subsequently loaded objectsThe dlopen
function returns a handle upon success. The handle should be used for the subsequent calls to dlsym
and dlclose
.
The dlclose
function simply decrements the reference count of the loaded library handle. If the reference count is reduced to zero, the library will be unloaded.
The dlerror
function returns a string to describe the most recent error occurred while calling dlopen
, dlsym
, or dlclose
since the last call to dlerror
. It returns NULL
if no such error occurred.
The dlsym
function returns the memory address of a given symbol of the loaded dynamic library referred by the input argument handle. The returned address can be used to access the symbol.
The dladdr
function takes an address and tries to
return more information about the address and library through the info
argument of the DI_info
type. The
DI_info
data structure is defined as shown in the following code snippet:
typedef struct { const char *dli_fname; void *dli_fbase; const char *dli_sname; void *dli_saddr; } Dl_info;
dli_fname
indicates the path of the shared object referred by the input argument addr
. The dli_fbase
is the address where the shared object is loaded. dli_sname
indicates the name of the nearest symbol with address lower than addr
, and dli_saddr
is the address of symbol named by dli_sname
.
In our example, we demonstrated the usage of the first four
functions. We load the math library by dlopen
, obtain the address of the sqrt
function by dlsym
, check the error by dlerror
, and close the library by dlclose
.
For more details on the dynamic loading library, refer to http://tldp.org/HOWTO/Program-Library-HOWTO/dl-libraries.html and http://linux.die.net/man/3/dlopen.