JNI 原理

2019-03-04  本文已影响0人  莫库施勒

我们都知道JNI结构是 Java 层 -> JNI -> Native 层, 以此实现Java 层和Native层可以互相调用

进程与DalivVM

每个应用程序都是由一个或多个进程组成,每个进程都对应着一个DalvikVM。DalvikVM是由代码native启动,在DalvikVM启动后,会返回一个JavaVM结构体。每个线程又对应着一个JNIEnv的结构体。也就是说整个进程都在native的管理之下,所以native可以非常容易的改变DalvikVM内部的数据。

总结

程序与进程是1:N,进程与DalivkVM是1:1,启动DalivkVM后会得到一个JavaVM,同时一个进程又对应n个线程,线程对应着一个JNIEnv的结构体,我们需要通过JavaVM和JNIEnv的机构来实现相互的访问,而JNIEnv内部包含一个Pointer来指向虚拟机的Function Table

实现

Java -> Native Android中每当一个java线程第一次要调用本地C/C++代码时,Dalvik虚拟机实例会为该java线程产生一个JNIEnv*指针

Native -> Java 本地的C/C++代码想获得当前线程所要使用的JNIEnv时,可以使用Dalvik VM对象的 JavaVMjvm -> GetEnv()方法,该方法会返回当前线程所在的JNIEnv。

Java <--> Native Java每个线程在和C/C++互相调用时,JNIEnv*是相互独立的,互不干扰的,提升了并发执行时的安全性。

具体分析

public class Test{
        static{
                System.loadLibrary("bridge");
        }
        public native int nativeAdd(int x,int y);
        public int add(int x,int y){
                return x+y;
        }
        public static void main(String[] args){
                Test obj = new Test();
                System.out.printf("%d\n",obj.nativeAdd(2012,3));
                System.out.printf("%d\n",obj.add(2012,3));
        }
}

我们这里有两个 add 方法,一个是 native 方法,一个是 Java 方法,我们查看一下它们在 class 中的字节码

  public native int nativeAdd(int, int);
    flags: ACC_PUBLIC, ACC_NATIVE

  public int add(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: ireturn       
      LineNumberTable:
        line 8: 0

普通的“add”方法是直接把字节码放到code属性表中,Java在执行的时候,可以通过找方法表,再找到相应的code属性表,最终解释执行代码。
而native方法,与普通的方法通过一个标志“ACC_NATIVE”区分开来。那么在执行的时候是怎么找到动态链接库的代码呢.
我们从 System.loadLibrary()开始

    public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
    }
     synchronized void loadLibrary0(Class<?> fromClass, String libname) {
        // ......中间省略......
        ClassLoader.loadLibrary(fromClass, libname, false);
    }
    // Invoked in the java.lang.Runtime class to implement load and loadLibrary.
    static void loadLibrary(Class<?> fromClass, String name,
                            boolean isAbsolute) {
        ......
        if (sys_paths == null) {
            usr_paths = initializePath("java.library.path");
            sys_paths = initializePath("sun.boot.library.path");
        }
        if (isAbsolute) {
            if (loadLibrary0(fromClass, new File(name))) {
                return;
            }
            ......
        }
        if (loader != null) {
            String libfilename = loader.findLibrary(name);
            if (libfilename != null) {
                ......
                if (loadLibrary0(fromClass, libfile)) {
                    return;
                }
                ......
            }
        }
        for (int i = 0 ; i < sys_paths.length ; i++) {
            ......
            if (loadLibrary0(fromClass, libfile)) {
                return;
            }
            ......
        }
        for (int i = 0 ; i < usr_paths.length ; i++) {
                ......
                if (loadLibrary0(fromClass, libfile)) {
                    return;
                }
                ......
            }
        ......
    }

    private static boolean loadLibrary0(Class<?> fromClass, final File file) {
        ......
        Vector<NativeLibrary> libs =
            loader != null ? loader.nativeLibraries : systemNativeLibraries;
        synchronized (libs) {
            int size = libs.size();
            for (int i = 0; i < size; i++) {
                ......
            }

            synchronized (loadedLibraryNames) {
                ......
                int n = nativeLibraryContext.size();
                for (int i = 0; i < n; i++) {
                    ......
                }
                NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
                nativeLibraryContext.push(lib);
                try {
                    lib.load(name, isBuiltin);
                } finally {
                    nativeLibraryContext.pop();
                }
                ......
            }
        }
    }
static class NativeLibrary {
        // opaque handle to native library, used in native code.
        long handle;
        ......
        // the loader this native library belongs.
        private final Class<?> fromClass;
        // the canonicalized name of the native library.
        // or static library name
        String name;
        // Indicates if the native library is linked into the VM
        boolean isBuiltin;
        // Indicates if the native library is loaded
        boolean loaded;
        native void load(String name, boolean isBuiltin);
        native long find(String name);
        native void unload(String name, boolean isBuiltin);
        ......
    }

我们发现 NativeLibrary 也是调用的 native 函数。我们从源码查找 load,find,unload 的 native 实现

// openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void * os::dll_load(const char *filename, char *ebuf, int ebuflen)
{
  ......
  bool load_attempted = false;
  ......
  if (!load_attempted) {
    result = os::Linux::dlopen_helper(filename, ebuf, ebuflen);
  }

  if (result != NULL) {
    // Successful loading
    return result;
  }
  ......
}
// openjdk/hotspot/src/os/linux/vm/os_linux.cpp
void * os::Linux::dlopen_helper(const char *filename, char *ebuf, int ebuflen) {
  void * result = ::dlopen(filename, RTLD_LAZY);
  ......
  return result;
}
// /usr/include/dlfcn.h
/* Open the shared object FILE and map it in; return a handle that can be
   passed to `dlsym' to get symbol values from it.  */
extern void *dlopen (const char *__file, int __mode) __THROW;

简单的说,dlopen、dlsym提供一种动态转载库到内存的机制,在需要的时候,可以调用库中的方法。

dlopen,dlsym 用法

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
//这里为了演示方便去掉错误检查相关的代码
int main(int argc,char*argv[]){
        void * handle;
        int (*func)(int,int);
        char *error;
        int a,b;

        //加载libhello.so库
        handle = dlopen("libhello.so",RTLD_LAZY);
        func = dlsym(handle,"add");

        //读取两个参数
        sscanf(argv[1],"%d",&a);
        sscanf(argv[2],"%d",&b);

        //输出结果
        printf("a + b = %d\n",(*func)(a,b));
        dlclose(handle);
}

总结

dlopen相当于打开一个共享库,打开的时候可以使用RTLD_LAZY标志,等函数真正被调用的时候才去装载库。dlopen返回一个handle,这个句柄是使用其它的dlxxx函数用的,dlsym就是使用dlopen返回的handle,去查找相应的符号,然后返回相应的函数指针的

如果回头去看看刚才分析的Java的NativeLibrary类,会发现里面有个handle成员!

Java动态装载共享库,靠的就是系统的 “dlxxx” 相关的函数实现的,在装载共享库的时候,读取共享库的 header,解析并保存里面的符号表,当调用native方法的时候,用刚才的例子中提到的方法进行调用。

上一篇下一篇

猜你喜欢

热点阅读