JNI 全称是 Java Native Interface。是在 Java 和 Native 层(包括但不限于C/C++)相互调用的接口规范。
JNI 在 Java 1.1中正式推出,在 Java 1.2版本中加入了 JNI_OnLoad、JNI_OnUnload 方法,这两个方法还是很有用的,后面再说。
JNI基础篇
Java 通过 JNI 调用本地方法的过程大致是:
写一个 Java 类,在其中声明对应要调用的 native 方法,用 native 关键字修饰。 比如 private static native int native_newInstance(); 通过 javah 命令生成 Java 类对应的 C/C++ 头文件。javah -encoding utf-8 -cp src com.young.soundtouch.SoundTouch; 在 C/C++ 中实现头文件中声明的函数; 编译 C/C++ 代码为动态库(Windows中的dll、Linux/Android 中的 so、MAC OSX 中的 dylib); 在 Java 代码中加载动态库,即可像调用 Java 方法一样,调用到 native 函数。 其中第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 文件开头就是普通的头文件,但是可以发现:
包含了 jni.h 头文件(一般位于 $JAVA_HOME/jd{jdk-version}/include 文目录内)。这是 JNI 中所有的类型、函数、宏等定义的地方。所以C/C++世界的JNI是由他制定的游戏规则。 在类中生命的常量(static final)类型会在头文件中以宏的形式出现,这一点还是很方便的。 函数的注释还是比较全的,包括了: 对应的 class 对应的 Java 方法名 对应 Java 方法的签名 方法的声明显得有点奇怪,由以下及部分组成: JNIEXPORT这是函数的导出方式; jint 返回值类型(jint 由 jni.h 定义,对应 int,下面具体再说吧); JNICALL 函数的调用方式也就是汇编级别参数的传入方式; Java_com_young_soundtouch_SoundTouch_native_1getDefaultSampleElementSize —— 超级长的函数名!!!格式是 Java_ + 类全名 + _ + JAVA 中声明的native方法名。其中会把包名中的点(.)替换成下划线(_),同时为了避免冲突把下划线替换成_1; 方法的参数,上面的这个方法在 Java 的声明中实际上是没有参数的,其中的 JNIENV 顾名思义是 JNI 环境,和具体的线程绑定。而第二个参数 jclass 其实是 Java 中的 Class。因为上面是一个 static 方法,因此第二个参数是 jclass。如果是一个实例方法则对应第二个参数是 jobject,相当于 Java中的 this。 下面在 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 中的一系列操作的接口函数。
JNI 中操作 jobject
其实也就是在native层操作 Java 层的实例。 要操作一个实例无疑是:
获取/设置 (即 get/set )成员变量(field)的值; 调用成员方法(method)。 所以问题来了:(挖掘机技术哪家强?! o(*≧▽≦)ツ┏━┓ )
怎么得到 field 和 method?
通过使用 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 就知道狗蛋住哪了,现在去狗蛋家找他玩 ♪(^∇^*)
- get:
GetField(jobject , jfieldID); 即可获得对应的field,其中field的类型是type,可以是上面类型所叙述的任何一种 GetStaticField(jobject , jfieldID); 同1,唯一的区别是用来获取静态成员。 2. set:
void SetField(jobject obj, jfieldID fieldID, val) void SetStaticField(jclass clazz, jfieldID fieldID, value); 成员方法:
调用方法自然要把方法的参数传递进去,JNI中实现了三种参数的传递方式:
CallMethod(jobject obj, jmethod jmethodID, ...) 其中 ... 是 C 中的可变长参数,类似于 printf 那样,可以传递不定长个参数。于是你可以把 Java 方法需要的参数在这里面传递进去。 CallMethodV(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.” CallMethodA(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;
- 调用实例方法(instance method):
CallMethod(jobject obj, jmethodID methodID, ...); 调用一个具有类型返回值的方法。 CallMethodV(jobject obj, jmethodID methodID, va_list args); CallMethodA(jobject obj, jmethodID methodID, const jvalue * args) 2. 调用静态方法(static method):
CallStaticMethod(jobject obj, jmethodID methodID, ...); CallStaticMethodV(jobject obj, jmethodID methodID, va_list args); CallStaticMethodA(jobject obj, jmethodID methodID, const jvalue * args) 3. 调用父类方法(super.method),这个就有点不一样了。多了一个 jclass 参数,jclass 可以使 obj 的父类,也可以是 obj 自己的class,但是 methodID 必须是从 jclass 获取到的,这样就可以调用到父类的方法。
CallNonvirtualMethod(jobject obj, jclass clazz, jmethodID methodID, ...) CallNonvirtualMethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args); CallNonvirtualMethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, const jvalue *args);
在这里给大家提供一个学习交流的平台,java架构师群: 867748702
具有1-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加群。
在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加群。
如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的可以加群。
加Java架构师进阶交流群获取Java工程化、高性能及分布式、高性能、深入浅出。高架构。 性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限 都是大牛带飞 让你少走很多的弯路的 群号是: 867748702对了 小白勿进 最好是有开发经验
注:加群要求
1、具有工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。
2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。
3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。
4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。
5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!