理解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 个函数,过程如下:

  1. 打开动态库,拿到一个动态库句柄
  2. 通过句柄和方法名获取方法指针地址
  3. 将方法地址强制类型转换成方法指针
  4. 调用动态库中的方法
  5. 通过句柄关闭动态库

g++ -std=c++11 -ldl main_call.cpp -o main

results matching ""

    No results matching ""