理解System.loadLibrary
[System.java]
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
此处 VMStack.getCallingClassLoader()拿到的是调用者的 ClassLoader,一般情况下是PathClassLoader。
ClasssLoader 存在的情况
[Runtime.java]
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}
...
}
[BaseDexClassLoader.java]
findLibrary是由PathClassLoader的父类BaseDexClassLoader去实现的。
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
[DexPathList.java] 首先,pathList对象是怎么来的?
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
this.dexElements =
makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
private static File[] splitLibraryPath(String path) {
/*
* Native libraries may exist in both the system and
* application library paths, and we use this search order:
*
* 1. this class loader's library path for application
* libraries
* 2. the VM's library path from the system
* property for system libraries
*
* This order was reversed prior to Gingerbread; see http://b/2933456.
*/
ArrayList<File> result = splitPaths(
path, System.getProperty("java.library.path", "."), true);
return result.toArray(new File[result.size()]);
}
so库可能存在于系统的或者应用库中,因此从这2个路径下进行搜索。
- 应用路径:
/data/app/${package-name}/lib
- 系统路径:
/vendor/lib:/system/lib
。如果是64位系统是/vendor/lib64:/system/lib64
。
[DexPathList.java#findLibrary]
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (File directory : nativeLibraryDirectories) {
File file = new File(directory, fileName);
if (file.exists() && file.isFile() && file.canRead()) {
return file.getPath();
}
}
return null;
}
[System.java]
public static String mapLibraryName(String nickname) {
if (nickname == null) {
throw new NullPointerException("nickname == null");
}
return "lib" + nickname + ".so";
}
至此完成了so库的查找。
回到 Runtime 的 loadLibrary 方法,通过 ClassLoader 找到目标文件之后会调用 doLoad 方法。 [Runtime.java]
private String doLoad(String name, ClassLoader loader) {
String librarySearchPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);
}
}
[java_lang_Runtime.cc]
static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
JValue* pResult)
{
StringObject* fileNameObj = (StringObject*) args[0];
Object* classLoader = (Object*) args[1];
char* fileName = NULL;
StringObject* result = NULL;
char* reason = NULL;
bool success;
assert(fileNameObj != NULL);
fileName = dvmCreateCstrFromString(fileNameObj);
success = dvmLoadNativeCode(fileName, classLoader, &reason);
if (!success) {
const char* msg = (reason != NULL) ? reason : "unknown failure";
result = dvmCreateStringFromCstr(msg);
dvmReleaseTrackedAlloc((Object*) result, NULL);
}
free(reason);
free(fileName);
RETURN_PTR(result);
}
Linux 系统加载动态库过程分析
Linux 环境下加载动态库主要包括如下方法,位于头文件#include <dlfcn.h>
中:
void *dlopen(const char *filename, int flag); //打开动态链接库
char *dlerror(void); //获取错误信息
void *dlsym(void *handle, const char *symbol); //获取方法指针
int dlclose(void *handle); //关闭动态链接库
生成动态库
[caculate.cpp]
extern "C"
int add(int a, int b) {
return a + b;
}
extern "C"
int mul(int a, int b) {
return a*b;
}
通过命令编译成动态链接库:
g++ -fPIC -shared caculate.cpp -o libcaculate.so
编写加载动态库的代码 [main_call.cpp]
#include <iostream>
#include <dlfcn.h>
using namespace std;
static const char * const LIB_PATH = "./libcaculate.so";
typedef int (*CACULATE_FUNC)(int, int);
int main() {
void* symAdd = nullptr;
void* symMul = nullptr;
char* errorMsg = nullptr;
dlerror();
//1.打开动态库,拿到一个动态库句柄
void* handle = dlopen(LIB_PATH, RTLD_NOW);
if(handle == nullptr) {
cout << "load error!" << endl;
return -1;
}
// 查看是否有错误
if ((errorMsg = dlerror()) != nullptr) {
cout << "errorMsg:" << errorMsg << endl;
return -1;
}
cout << "load success!" << endl;
//2.通过句柄和方法名获取方法指针地址
symAdd = dlsym(handle, "add");
if(symAdd == nullptr) {
cout << "dlsym failed!" << endl;
if ((errorMsg = dlerror()) != nullptr) {
cout << "error message:" << errorMsg << endl;
return -1;
}
}
//3.将方法地址强制类型转换成方法指针
CACULATE_FUNC addFunc = reinterpret_cast(symAdd);
//4.调用动态库中的方法
cout << "1 + 2 = " << addFunc(1, 2) << endl;
//5.通过句柄关闭动态库
dlclose(handle);
return 0;
}
要就用了上面提到的 4 个函数,过程如下:
- 打开动态库,拿到一个动态库句柄
- 通过句柄和方法名获取方法指针地址
- 将方法地址强制类型转换成方法指针
- 调用动态库中的方法
- 通过句柄关闭动态库
g++ -std=c++11 -ldl main_call.cpp -o main