JNI 全称是 Java Native Interface。是在 Java 和 Native 层(包括但不限于C/C++)相互调用的接口规范。
JNI 在 Java 1.1中正式推出,在 Java 1.2版本中加入了 JNI_OnLoad、JNI_OnUnload 方法,这两个方法还是很有用的,后面再说。
Java 通过 JNI 调用本地方法的过程大致是:
native
关键字修饰。 比如 private static native int native_newInstance();
javah
命令生成 Java 类对应的 C/C++ 头文件。 javah -encoding utf-8 -cp src com.young.soundtouch.SoundTouch;
其中第3步在 Java 1.2 中增加了 JNI_OnLoad
方法之后有另一种实现方式(后面说)。
javah 生成的头文件大致是这样的:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_young_soundtouch_SoundTouch */ #ifndef _Included_com_young_soundtouch_SoundTouch #define _Included_com_young_soundtouch_SoundTouch #ifdef __cplusplus extern "C" { #endif #undef com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER #define com_young_soundtouch_SoundTouch_SETTING_USE_AA_FILTER 0L /* * Class: com_young_soundtouch_SoundTouch * Method: native_getDefaultSampleElementSize * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_young_soundtouch_SoundTouch_native_1getDefaultSampleElementSize (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif
文件开头就是普通的头文件,但是可以发现:
下面在 C/C++ 中实现这个方法就行啦。但是在动手前现大致了解以下 jni.h 制定的游戏规则。
javah 生成的头文件里面使用的类型都是 jni.h 定义的,目的是做到 平台无关 ,比如保证在所有平台上 jint 都是32位的有符号整型。
通过表格发现,除了上面定义的 String
、 Class、
Throwable
,其他的类(除了数组)都是以 jobject
的形式出现的!事实上 jstring、 jclass 也都是 object 的子类。所以这里还是和 Java 层一样,一切皆 jobject。(当然,如果 jni 在 C 语言中编译的话是没有继承的概念的,此时 jstring、jclass 等其实就是 jobject!用了 typedef 转换而已!!)
接下来是 JNIEnv *
这个指针,它提供了 JNI 中的一系列操作的接口函数。
其实也就是在native层操作 Java 层的实例。 要操作一个实例无疑是:
所以问题来了:(挖掘机技术哪家强?! o(*≧▽≦)ツ┏━┓ )
通过使用 jfieldID 和 jmethodID : 在 JNI 中使用类似于放射的方式来进行 field 和 method 的操作。JNI 中使用j fieldID 和 jmethodID 来表示成员变量和成员方法,获取方式是:
jfieldID GetFieldID(jclass clazz, const char *name, const char *sig); jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig); jmethodID GetMethodID(jclass clazz, const char *name, const char *sig); jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig) ;
其中最后一个参数是 签名 。 获取jclass的方法 除了实用上面静态方法的第二个参数外,还可以手动获取。 jclass FindClass(const char *name)
需要注意的是 name
参数,他是一个类包括包名的全称,但是需要把包名中的点 .
替换成斜杠 /
。(好吧,事实上我不是太明白为啥要这么做。)
有了 jfieldID 和 jmethodID 就知道狗蛋住哪了,现在去狗蛋家找他玩 ♪(^∇^*)
1. get:
<type> Get<type>Field(jobject , jfieldID);
即可获得对应的field,其中field的类型是type,可以是上面 类型 所叙述的任何一种 <type> GetStatic<type>Field(jobject , jfieldID);
同1,唯一的区别是用来获取静态成员。 2. set:
调用方法自然要把方法的参数传递进去,JNI中实现了三种参数的传递方式:
Call<type>Method(jobject obj, jmethod jmethodID, ...)
其中 ...
是 C 中的可变长参数,类似于 printf
那样,可以传递不定长个参数。于是你可以把 Java 方法需要的参数在这里面传递进去。 Call<type>MethodV(jobject obj, jmethodID methodID, va_list args)
其中的 va_list
也是 C 中可变长参数相关的内容(我不了解,不敢瞎说,偷懒粘一下Oracle的文档)“Programmers place all arguments to the method in an args argument of type va_list that immediately follows the methodID argument. The CallMethodV routine accepts the arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.” Call<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)
哎!这个我知道可以说两句 LOL ~~这里的 jvalue
通过查代码发现就是 JNI 中各个数据类型的 union,所以可以使用任何类型复制!所以参数的传入方式是通过一个 jvalue 的数组,数组内的元素可以是任何 jni 类型。 然后问题又来了:(挖掘机技术到底哪家强?!o(*≧▽≦)ツ┏━┓) 如果传进来的参数和java声明的参数的不一致会怎么样!(即不符合方法签名)这里文档中没用明确解释,但是说道: > Exceptions raised during the execution of the Java method.
typedef union jvalue { jboolean z; jbyte b; jchar c; jshort s; jint i; jlong j; jfloat f; jdouble d; jobject l; } jvalue;
1. 调用实例方法(instance method):
<type> Call<type>Method(jobject obj, jmethodID methodID, ...);
调用一个具有 <type>
类型返回值的方法。 2. 调用静态方法(static method):
<type> CallStatic<type>Method(jobject obj, jmethodID methodID, ...); <type> CallStatic<type>MethodV(jobject obj, jmethodID methodID, va_list args); CallStatic<type>MethodA(jobject obj, jmethodID methodID, const jvalue * args)
3. 调用父类方法(super.method),这个就有点不一样了。多了一个 jclass 参数,jclass 可以使 obj 的父类,也可以是 obj 自己的class,但是 methodID 必须是从 jclass 获取到的,这样就可以调用到父类的方法。
<type> CallNonvirtual<type>Method(jobject obj, jclass clazz, jmethodID methodID, ...) <type> CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); <type> CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);