一个简单的例子让我们初步地了解JNI的作用,但是关于JNI中的一些概念还是需要了解清楚,才能够更好的去利用它来实现我们想要做的事情。
那么C++和Java之间的是如何通过JNI来进行互相调用的呢?
我们知道,在Android中,当Java文件被编译成dex文件之后,会由类加载器加载到Dalvik VM(DVM)中,由DVM来进行解释,翻译成机器语言之后,才能由机器来运行。
而对于C/C++来说,其源代码经由Android提供的NDK工具包,可以编译成可执行动态库(即.so文件),之后,Java和C++之间就可以进行通讯了。
那么,在这里,可以想像,Java的Dex字节码和C/C++的so库肯定是同时运行在一个DVM之中,它们是共同使用一个进程空间的,否则,它们怎么彼此沟通呢?
所以在这里,一个关键的中间区域就是Dalvik VM。而对于C/C++,当它们也被加载进DVM之后,由C/C++实现的函数方法等都会被加载在DVM中的函数表中。
如果想要在C/C++中调用函数,它们必须要有个东西能够让其访问到这个虚拟机中的函数表。
而这个东西就是JNIEnv *。
当我们利用javah生成的C/C++的头文件的时候,如下:
JNIEXPORT jstring JNICALL Java_com_lms_jni_HwDemo_printHello
(JNIEnv *e, jobject j)
{
return (**e).NewStringUTF(e,"Hello from T" );
}
我们可以看到这个方法有两个参数,其中第一个就是JNIEnv *,而我们在Java端定义这个方法的时候,是没有参数的,如下:
public native String printHello();
那么这个JNIEnv是干什么用的?
其实从这个参数的名称就可以看到,就是指JNI的运行环境,我觉得它就是对Java虚拟环境的一个引用,在Android中,就是指Dalvik VM。
参考jni.h文件中关于JNIEnv的定义,如下(对于C和C++,它的定义有点不一样):
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //C++中JNIEnv的类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; //C中JNIEnv的类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif
在C中,我们可以看到JNIEnv的类型就是JNINativeInterface* ,是一个指针类型,那么在C++中呢,_JNIEnv是什么样的呢?
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
而对于C++来说, _JNIEnv是一个结构体,里面包含了JNINativeInterface*的结构。
所以从这里也可以看到,对于C和C++来说,它们引用JNIEnv中的方法是有一点不一样的。
总的来说,JNIEnv,不管是C,还是C++,其实关键都是JNINativeInterface的这个结构。
我们可以简单看一下JNINativeInterface结构的定义,如下:
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
jfieldID (*FromReflectedField)(JNIEnv*, jobject);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
}
可以看到在它其中定义了很多的函数指针,而通过这些定义,JNI层其实就获得了对DVM的引用,通过定义的这些函数指针,可以定位到虚拟机中的 JNI 函数表,从而实现JNI层在DVM中的函数调用。
所以,可以这样理解,其实JNIEnv,就是对DVM运行环境中C/C++函数的一个引用,而也正因为此,当C/C++想要在DVM中调用函数的时候,由于其是在DVM的环境中,所以它们必须通过JNIEnv* 这个参数来获得这些方法,之后才能够使用。
那么这个JNIEnv是什么时候产生的呢?
当Android中第一个Java线程要调用本地的C/C++代码的时候,DVM就会为该线程产生一个JNIEnv*的指针。而每一个线程在和C/C++互相调用的时候,其对应的JNIEnv 也是相互独立。
原文地址: http://blog.youkuaiyun.com/linmiansheng/article/details/26146231