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 也是相互独立。
嗯,结束。