JNI 的学习(一)对于 JNIEnv 的一些认识

JNI 的学习(一)对于 JNIEnv 的一些认识

  Java 通过 JNI 机制调用 c/c++ 写的 native 程序。c/c++ 开发的 native 程序需要遵循一定的 JNI 规范,下面的例子就是一个 JNI 函数声明:

1 JNIEXPORT jstring JNICALL Java_com_clay_example_JNITest_getJNIString
2 (JNIEnv* env, jobject obj)

  JVM 负责从Java Stack 转入 C/C++ Native Stack。当 Java 进入 JNI 调用,除了函数本身的参数(arg0),会多出两个参数:JNIEnv 指针和 jobject 指针。而我们在Java端定义这个方法的时候,是没有参数的,如下:

1 public native String printHello();

  JNIEnv 指针是 JVM 创建的,用于 Native的 c/c++ 方法操纵 Java 执行栈中的数据,比如 Java Class, Java Method 等。

  首先,JNI 对于JNIEnv 的使用, 提供了两种语法: c 语法以及 c++ 语法,如下:

  c 语法:

1 jsize len = (*env)->GetArrayLength(env,array);

  c++ 语法:

1 jsize len =env->GetArrayLength(array);
  那么这个 JNIEnv 是干什么用的?
  其实从这个参数的名称就可以看到,就是指 JNI 的运行环境,我觉得它就是对 Java 虚拟环境的一个引用,在 Android 中,就是指 Dalvik VM。
  参考 jni.h 文件中关于 JNIEnv 的定义,如下(对于 C 和 C++,它的定义有点不一样):
 1 struct _JNIEnv;
 2 struct _JavaVM;
 3 typedef const struct JNINativeInterface* C_JNIEnv;
 4 
 5 #if defined(__cplusplus)
 6 typedef _JNIEnv JNIEnv;  //从这里可以看出 C++ 中对 JNIEnv 的使用就是对结构体的使用
 7 typedef _JavaVM JavaVM;
 8 #else
 9 typedef const struct JNINativeInterface* JNIEnv;  //从这里可以看出 C 中队 JNIEnv 的使用就是对结构体指针的使用
10 typedef const struct JNIInvokeInterface* JavaVM;
11 #endif

  在 C 中,我们可以看到 JNIEnv 的类型就是 JNINativeInterface* ,是一个指针类型,那么在 C++中呢,_JNIEnv 是什么样的呢?

 1 /*
 2  * C++ object wrapper. c++对象包装器。
 3  *
 4  * This is usually overlaid on a C struct whose first element is a
 5  * JNINativeInterface*.  We rely somewhat on compiler behavior. 这通常覆盖在第一个元素是 JNINativeInterface* 的 C 结构上。我们多少依赖于编译器的行为。
 6  */
 7 struct _JNIEnv {
 8     /* do not rename this; it does not seem to be entirely opaque */
 9     const struct JNINativeInterface* functions;
10 
11 #if defined(__cplusplus)
12 
13     jint GetVersion()
14     { return functions->GetVersion(this); }

  而对于 C++ 来说, _JNIEnv 是一个结构体,里面包含了 JNINativeInterface* 的结构。

  所以从这里也可以看到,对于 C 和 C++ 来说,它们引用 JNIEnv 中的方法是有一点不一样的。 总的来说,JNIEnv,不管是 C,还是 C++,其实关键都是 JNINativeInterface 的这个结构。

  我们可以简单看一下 JNINativeInterface 结构的定义,如下:(删减的~~~)

  1 /*
  2  * Table of interface function pointers.
  3  */
  4 struct JNINativeInterface {
  5     void*       reserved0;
  6     void*       reserved1;
  7     void*       reserved2;
  8     void*       reserved3;
  9 
 10     jint        (*GetVersion)(JNIEnv *);
 11 
 12     jclass      (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
 13                         jsize);
 14     jclass      (*FindClass)(JNIEnv*, const char*);
 15 
 16     jmethodID   (*FromReflectedMethod)(JNIEnv*, jobject);
 17     jfieldID    (*FromReflectedField)(JNIEnv*, jobject);
 18     /* spec doesn't show jboolean parameter */
 19     jobject     (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
 20 
 21    ......
 22 
 23     jboolean    (*ExceptionCheck)(JNIEnv*);
 24 
 25     jobject     (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
 26     void*       (*GetDirectBufferAddress)(JNIEnv*, jobject);
 27     jlong       (*GetDirectBufferCapacity)(JNIEnv*, jobject);
 28 
 29     /* added in JNI 1.6 */
 30     jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
 31 };
  可以看到在它其中定义了很多的函数指针,而通过这些定义,JNI 层其实就获得了对 DVM 的引用,通过定义的这些函数指针,可以定位到虚拟机中的 JNI 函数表,从而实现 JNI 层在 DVM 中的函数调用。
  所以,可以这样理解,其实 JNIEnv,就是对 DVM 运行环境中 C/C++ 函数的一个引用,而也正因为此,当 C/C++ 想要在 DVM 中调用函数的时候,由于其是在 DVM 的环境中,所以它们必须通过 JNIEnv*  这个参数来获得这些方法,之后才能够使用。
  那么这个 JNIEnv 是什么时候产生的呢?
  当 Android 中第一个 Java 线程要调用本地的 C/C++ 代码的时候,DVM 就会为该线程产生一个 JNIEnv* 的指针。而每一个线程在和 C/C++ 互相调用的时候,其对应的 JNIEnv 也是相互独立。
  嗯,结束。
原文地址:https://www.cnblogs.com/Reverse-xiaoyu/p/14117991.html