在上一篇文章中对JNI简单介绍了,在这篇文章中将对JNI原理进行介绍。本篇文章将以JNI执行环境、JNI数据类型、JNI注册方式、JNI引用、JNI变量共享以及JNI调用方式来介绍JNI原理。
一、执行环境(Runtime)
在计算机中,每种编程语言都有一个执行环境(Runtime),执行环境用来解释执行语言的语句。在JNI开发中有两个比较重要与执行环境Runtime相关的变量:
JavaVM和
JNIEnv。
- JavaVM
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
在jni.h文件中也定义了JavaVM的数据结构,可以看到JavaVM结构中封装了一些函数指针,这些函数指针主要是对JVM操作的接口,定义如下:
#if defined(__cplusplus)
typedef _JavaVM JavaVM; //C++的JavaVM定义
#else
typedef const struct JNIInvokeInterface* JavaVM; //C的JavaVM定义
#endif
/*
* JNI invocation interface.
*/
struct JNIInvokeInterface {
void* reserved0;//保留
void* reserved1;//保留
void* reserved2;//保留
jint (*DestroyJavaVM)(JavaVM*); // 销毁Java虚拟机并回收资源
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);//链接到当前Java线程
jint (*DetachCurrentThread)(JavaVM*); //从当前Java线程中分离
jint (*GetEnv)(JavaVM*, void**, jint);// 获得当前线程的Java运行环境
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); // 将当前线程作为守护线程
};
/*
* C++ version.
*/
struct _JavaVM {
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};
从代码可以看到,JNIInvokeInterface结构封装了几个和JVM相关的函数。另外在C和C++中,JavaVM的定义是不相同的,在C中的JavaVM是JNIInvokeInterface类型指针,而在C++语言中,JavaVM是在JNIInvokeInterface指针上进行了一次封装,函数调用时少了一个参数。
JavaVM是JVM在JNI层的代表,在JNI层中有且仅有一个JavaVM。JavaVM是进程相关的,一个进程只有一个JavaVM。
- JNIEnv
JNIEnv不能跨线程,只在当前线程中有效。JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方法,传入的JNIEnv是相同的。但是一个本地方法可以被不同的Java线程调用,
因此本地方法可以接受不同的JNIEnv。
JNIEnv有两个作用:一个是调用Java函数,JNIEnv代表当前Java线程的运行环境,通过JNIEnv可以调用Java中的代码;另一个是操作Java对象,Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象。
JNIEnv的数据结构是在jni.h文件中定义的,可以看到JNIEnv数据结构也是一个函数表,在本地代码中通过JNIEnv的函数表来操作Java数据或调用Java方法。也就是说,只要在本地代码中拿到了JNIEnv结构,就可以在本地代码中调用Java代码。
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;//C++定义
#else
typedef const struct JNINativeInterface* JNIEnv;//C定义
#endif
/*
* Table of interface function pointers.
*/
//C JNIEnv定义
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jclass (*FindClass)(JNIEnv*, const char*);
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*,const char*);
.......
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, jint); //注册本地方法
jint (*UnregisterNatives)(JNIEnv*, jclass); //反注册本地方法
jint (*GetJavaVM)(JNIEnv*, JavaVM**); //获取对应的JavaVM对象
......
}
/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
//C++的JNIEnv定义
struct _JNIEnv {
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
.......
}
从代码可知,JNIEnv和JavaVM类似,也是定义了一些函数指针,通过这些函数可以操作Java对象。JNIEnv在C和C++中定义的方式也不相同,C++对JNINativeInterface指针进行了一次封装,调用时更加方便。
总的来说, JNI其实就是定义了Java语言和本地语言之间的一种沟通方式,这种沟通方式依赖于JavaVM和JNIEnv结构中定义的函数表,这些函数将Java中的方法调用转换为本地语言的函数调用。
JNI是JVM实现的一部分,JavaVM是JVM在JNI层的代表,每个Java线程都有一个JNIEnv,代表当前线程的执行环境,他们之间的关系如下图所示:
二、JNI数据类型
当Java与Native语言相互调用时,肯定会涉及到数据的传递。这两者属于不同的编程语言,在数据类型上有很多差异的。例如,在C语言中,int类型的长度取决于平台,char类型为1个字节长度,而在Java语言中,int类型固定为4个字节,而char类型为2个字节。为了使Java语言数据类型与Native语言数据类型匹配,需要借助JNI的数据类型来保证它们两者之间的数据类型和数据空间大小匹配。JNI中定义的一些数据类型有基本数据类型和引用数据类型。
2.1 JNI基本类型
Java类型
|
Native类型 |
JNI类型
|
描述
|
boolean |
unsigned char
|
jboolean
|
无符号8比特
|
byte |
signed char
|
jbyte
|
有符号8比特 |
char |
unsigned short
|
jchar
|
无符号16比特
|
short
|
short
|
jshort
|
有符号16比特
|
int
|
int
|
jint
|
有符号32比特
|
long
|
long long
|
jlong
|
有符号64比特
|
float
|
float
|
jfloat
|
32比特
|
double
|
double
|
jdouble
|
64比特
|
void |
void
|
void
|
N/A
|
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
typedef jint jsize;
需要注意的是jchar代表的是Java的char类型,对应于C/C++中的却是unsigned short类型,因为Java中的char类型是两个字节,jchar相当于C/C++的宽字符。如果需要在本地方法中定义一个jchar类型的数据,规范的写法应该是jchar = L'C'。
实际上,所有带j的JNI类型,都是JNI对应的Java类型,并且JNI的类型接口与本地代码在类型的空间大小上是完全匹配的,而在语言层次上却不一定相同。在本地方法中与JNI接口调用时,要在内部进行转换,必须小心处理。
2.2 JNI引用类型
在本地代码中为了访问Java运行环境中的引用类型,在JNI中也定义了一套对应的引用类型,他们的对应关系如下:
所有的引用类型对应于jobject,java.lang.Class类型对应于jclass,数组类型对应于jarray,java.lang.Throwable类型对应于jthrowable,Object数组对应于jobjectArray。
JNI引用类型都是以j开头的,与Java中所