一、 jni.h 文件详解
1 jni.h头文件
Java Native Interface (JNI)头文件路径:java/jdk/include/jni.h
Java Native Interface头文件是Java开发工具包(Java Development Kit,JDK)中的一部分。JNI允许Java代码与其他编程语言(如C或C++)进行交互,通过使用JNI.h头文件,开发者可以访问和使用底层的本地功能和库。
- 定义了JavaVM、JNIEnv
- 定义了基本数据类型、数组类型
- 定义了方法签名公共类型
- 定义了属性ID、方法ID
- 定义了 JNINativeInterface 结构体、 JNIEnv 结构体(C++)
1.1 JavaVM、JNIEnv
JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;
即在每个线程中都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv。 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;
JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针, 指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组, 这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;
JNIEnv 与 JavaVM : 注意区分这两个概念;
-
JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, 一个JVM中只有一个JavaVM实例,这个实例是线程共享的;
-
JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
在 C 语言 和 C++ 中 , JNIEnv 代表着不同的含义 ;
#if defined(__cplusplus) //声明 C++ 环境下的 JNIEnv 类型 ;
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM; //将 _JNIEnv 结构体类型声明为 JNIEnv 类型
#else //声明 C 语言环境下的 JNIEnv 类型 ;
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM; //将 JNINativeInterface 结构体指针 类型 声明为 JNIEnv 类型
#endif
-
C 语言中的 JNIEnv * env 实际上是 JNINativeInterface** 类型。调用其中的方法指针时 , 先解引用得到其一维指针 , 然后调用对应的函数指针: (* env)->函数指针 。
-
C++ 中专门定义了 JNIEnv 结构体类型,JNIEnv结构体的函数直接封装调用了 JNINativeInterface 结构体中的函数指针 , 只需要调用 JNIEnv 结构体中的方法即可。 C++ 中的 JNIEnv * env , 可以将其当做一个对象使用直接调用其中的方法: env->方法名称;
总结 : JNI 中定义的函数指针 , 实际都定义在 JNINativeInterface 结构体中 ;
JNIEnv 的创建和释放
JNIEnv 创建 和 释放 : 定义在 JavaVM结构体中
-
调用 (AttachCurrentThread)(JavaVM, JNIEnv*, void) 方法, 创建 JNIEnv结构体;
-
调用 (DetachCurrentThread)(JavaVM)方法, 释放本线程中的 JNIEnv;
1.2 基本数据类型、数组类型的定义
/* jni_md.h contains the machine-dependent typedefs for jbyte, jint and jlong */
typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;
#ifndef JNI_TYPES_ALREADY_DEFINED_IN_JNI_MD_H
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
#ifdef __cplusplus
class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};
typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;
#else
struct _jobject;
typedef struct _jobject *jobject;//代表了Java中包含native方法的类的一个实例
typedef jobject jclass; //jclass代表的是一个类对象
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
#endif
1.3 方法签名公共类型的定义
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
//Object对象 L开头,包名/类名,”;”结尾,$标识嵌套类
//数组 [内部类型 如果是普通类型的数组不需要加“;”后缀,如果是Object类型的数组则需要添加”;”
1.4 属性ID、方法ID的定义
struct _jfieldID;
typedef struct _jfieldID *jfieldID;
struct _jmethodID;
typedef struct _jmethodID *jmethodID;
1.5 JNINativeInterface_ 结构体的定义
JNINativeInterface_结构体类型中定义了 229 个函数指针 , 用于处理 Java 层 与 Native 层的数据交互信息
struct JNINativeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
void *reserved3;
jint (JNICALL *GetVersion)(JNIEnv *env);
jclass (JNICALL *DefineClass)
(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
jsize len);
jclass (JNICALL *FindClass)
(JNIEnv *env, const char *name);
jmethodID (JNICALL *FromReflectedMethod)
(JNIEnv *env, jobject method);
jfieldID (JNICALL *FromReflectedField)
(JNIEnv *env, jobject field);
jobject (JNICALL *ToReflectedMethod)
(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);
jclass (JNICALL *GetSuperclass)
(JNIEnv *env, jclass sub);
jboolean (JNICALL *IsAssignableFrom)
(JNIEnv *env, jclass sub, jclass sup);
jobject (JNICALL *ToReflectedField)
(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);
......
};
1.6 _JNIEnv 结构体
_JNIEnv 结构体中封装了一个 JNINativeInterface 结构体类型指针 , _JNIEnv 结构体中也封装了 229 个 方法 , 每个方法都调用 对应的 JNINativeInterface functions 中的函数指针 ;*
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
jmethodID FromReflectedMethod(jobject method) {
return functions->FromReflectedMethod(this,method);
}
jfieldID FromReflectedField(jobject field) {
return functions->FromReflectedField(this,field);
}
jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) {
return functions->ToReflectedMethod(this, cls, methodID, isStatic);
}
jclass GetSuperclass(jclass sub) {
return functions->GetSuperclass(this, sub);
}
jboolean IsAssignableFrom(jclass sub, jclass sup) {
return functions->IsAssignableFrom(this, sub, sup);
}
......
}
二、 Jni函数总结
以下以 JNINativeInterface 结构体中函数为例,在C++中去除第一个参数即可。
2.1 访问Java对象
-
jclass (JNICALL *GetObjectClass) (JNIEnv *env, jobject obj);
获取对象所属的类,object对隐式this参数对象的引用 -
jclass (JNICALL *FindClass) (JNIEnv *env, const char *name);
以字符串形式来指定类名 -
jclass (JNICALL *GetSuperClass)(JNIEnv *env,jclass obj)
通过jclass可以获取其父类的jclass实例。
2.2 访问Java对象的域
-
jfieldID (JNICALL *GetFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
返回类中一个域的标识符 -
jfieldID (JNICALL *GetStaticFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);
返回类中一个静态域的标识符 -
void (JNICALL *SetDoubleField) (JNIEnv *env, jobject obj, jfieldID fieldID, jdouble val);
设置域的值 -
jdouble (JNICALL *GetDoubleField) (JNIEnv *env, jobject obj, jfieldID fieldID);
返回域的double值 -
jint (JNICALL *GetStaticIntField) (JNIEnv *env, jclass clazz, jfieldID fieldID);
返回域的double值
2.3 调用Java方法
2.3.1 调用实例方法(非静态)
- j_class class_PrintWriter = (*env)->GetObjectClass(env, object);
获取隐式参数this的类 - j_methodID id_print = (*env)->GetMethodID(env, class_PrintWriter, “print”, “(Ljava/lang/String;)V”);
获取方法ID - (*env)->CallVoidMethod(env, object, id_print, str);
根据返回类型,可以使用不同的CallxxxMethod,该方法从C中调用任何Java方法。
2.3.2 调用静态方法
- jclass class_System = (*env)->FindClass(env, “java/lang/System”);
获取指定的类 - jmethodID id_getProperty = (*env)->GetStaticMethodID(env, class_System, “getProperty”,
“(Ljava/lang/String;)Ljava/lang/String;”);
根据名称和描述符获取方法ID - jobject obj_ret = (*env)->CallStaticObjectMethod(env, class_System, id_getProperty,(*env)->NewStringUTF(env, “java.class.path”));
根据类、方法ID和入参调用静态方法
2.3.3 调用构造器
- jclass class_String = (*env)->FindClass(env, “java/lang/String”);
- jmethodID id_String = (*env)->GetMethodID(env, class_String, “”, “(Ljava/lang/String;)V”);
通过指定方法名“”,并指定构造器的方法签名,获得放方法ID - jobject obj_String = (*env)->NewObject(env, class_String, id_String, para);
调用构造器方法构造String对象
2.4 数组处理函数
- GetArrayLength
返回数组长度 - GetObjectArrayElement
返回数组元素值 - SetObjectArrayElement
设置新值 - GetxxxArrayElements
生成一个指向Java数组元素的C指针。域类型必须是基本类型。指针不再使用时,必须ReleasexxxArrayElements - ReleasexxxArrayElements
通知JVM GetxxxArrayElements获得的指针不再需要 - GetxxxArrayRegion
将Java数组元素复制到C数组,域类型必须是基本类型 - SetxxxArrayRegion
将C数组元素复制到Java数组
三、加载、卸载本地方法
JVM提供了一种方式允许程序员在加载动态链接库文件的时候做一些自定义操作。
- JNI_OnLoad()函数是在动态库被加载时调用。
- JNI_OnUnload()函数则是在本地库被卸载时调用。
所以这两个函数就是一个本地库最重要的两个管理生命周期的函数。
//依旧定义在JNI头文件:java/jdk/include/jni.h
/* Defined by native libraries. */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved);
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved);