概述jni(Java Native Interface)是什么?
它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。
jni主要解决java限制的事情,或者要进行高效开发。
最简单的jni使用
package com.dongnaoedu.jni;
public class JniTest {
public native static String getStringFromC();
public static void main(String[] args) {
String text = getStringFromC();
System.out.println(text);
}
//加载动态库
static{
System.loadLibrary("jni_demo");
}
}
头文件:
#include <jni.h>
#ifndef _Included_com_dongnaoedu_jni_JniTest
#define _Included_com_dongnaoedu_jni_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_dongnaoedu_jni_JniTest
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
实现:
#include "com_dongnaoedu_jni_JniTest.h"
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_getStringFromC
(JNIEnv *env, jclass jcls){
//JNIEnv 在C++中就是一个结构体的别名
//env 在C++中一个结构体的一级指针
return env->NewStringUTF("C String");
}
注意的细节
具体介绍:
每个native函数,都至少有两个参数(JNIEnv*,jclass或者jobject)
- 1)当native方法为静态方法时:
jclass 代表native方法所属类的Class对象(JniTest.class) - 2)当native方法为非静态方法时:
jobject 和一个数组和java.lang.Object类或它的子类的实例对应
基本数据
- Java基本数据类型与JNI数据类型的映射关系
- Java类型->JNI类型->C类型
基本数据类型
基本类型
boolean jboolean
byte jbyte;
char jchar;
short jshort;
int jint;
long jlong;
float jfloat;
double jdouble;
void void
引用类型(对象)
String jstring
object jobject
数组,基本数据类型的数组
byte[] jByteArray
对象数组
object[](String[]) jobjectArray
java代码:
package com.dongnaoedu.jni;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import javax.sql.rowset.CachedRowSet;
public class JniTest {
public String key = "jason";
public Human human = new Man();
public static int count = 9;
public native static String getStringFromC();
public native String getString2FromC(int i);
//访问属性,返回修改之后的属性内容
public native String accessField();
public native void accessStaticField();
public native void accessMethod();
public native void accessStaticMethod();
public native Date accessConstructor();
public native void accessNonvirtualMethod();
public native String chineseChars(String in);
public native void giveArray(int[] array);
public native int[] getArray(int len);
public native void localRef();
public native void createGlobalRef();
public native String getGlobalRef();
public native void deleteGlobalRef();
public native void exeception();
public native void cached();
public native static void initIds();
public static void main(String[] args) {
String text = getStringFromC();
System.out.println(text);
JniTest t = new JniTest();
text = t.getString2FromC(6);
System.out.println(text);
System.out.println("key修改前:"+t.key);
t.accessField();
System.out.println("key修改后:"+t.key);
System.out.println("count修改前:"+count);
t.accessStaticField();
System.out.println("count修改后:"+count);
t.accessMethod();
t.accessStaticMethod();
t.accessConstructor();
t.accessNonvirtualMethod();
System.out.println(t.chineseChars("宋喆"));
int[] array = {9,100,10,37,5,10};
//排序
t.giveArray(array);
for (int i : array) {
System.out.println(i);
}
//----------
int[] array2 = t.getArray(10);
System.out.println("------------");
for (int i : array2) {
System.out.println(i);
}
System.out.println("------08-17------");
t.createGlobalRef();
System.out.println(t.getGlobalRef());
//用完之后释放
t.deleteGlobalRef();
System.out.println("释放完了...");
//System.out.println(t.getGlobalRef());
try {
t.exeception();
} catch (Exception e) {
System.out.println("发生异常:"+e.getMessage());
}
System.out.println("--------异常发生之后-------");
try {
t.exeception();
} catch (Exception e) {
//e.printStackTrace();
System.out.println(e.getMessage());
}
//不断调用cached方法
for (int i = 0; i < 100; i++) {
t.cached();
}
}
//产生指定范围的随机数
public int genRandomInt(int max){
System.out.println("genRandomInt 执行了...");
return new Random().nextInt(max);
}
//产生UUID字符串
public static String getUUID(){
return UUID.randomUUID().toString();
}
//加载动态库
static{
System.loadLibrary("jni_study");
initIds();
}
}
#define _CRT_SECURE_NO_WARNINGS
#include "com_dongnaoedu_jni_JniTest.h"
#include <string.h>
//返回字符串
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_getStringFromC
(JNIEnv *env, jclass jcls){
return (*env)->NewStringUTF(env,"C String");
}
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_getString2FromC
(JNIEnv *env, jobject jobj, jint num){
return (*env)->NewStringUTF(env,"C String2");
}
//C/C++访问Java的成员
//修改属性key
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_accessField(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);//得到JniTest.class对象
jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");//得到字段jfieldID
jstring jstr = (*env)->GetObjectField(env, jobj, fid);//获取jfieldID对应字段的属性值
char *c_str = (*env)->GetStringUTFChars(env,jstr,JNI_FALSE);//将字符串转化
//拼接得到新的字符串
char text[20] = "super ";
strcat(text,c_str);
jstring new_jstr = (*env)->NewStringUTF(env, text); //c字符串 ->jstring
(*env)->SetObjectField(env, jobj, fid, new_jstr);//设置字段的值
return new_jstr;
}
//访问静态属性
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_accessStaticField(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);//得到JniTest.class对象
jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");//jfieldID
jint count = (*env)->GetStaticIntField(env, cls, fid); //GetStatic<Type>Field
count++;
(*env)->SetStaticIntField(env,cls,fid,count);//SetStatic<Type>Field
}
//2.访问java方法
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_accessMethod(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");//jmethodID
jint random = (*env)->CallIntMethod(env, jobj, mid, 200);//Call<Type>Method
}
//静态方法
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_accessStaticMethod(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);//jclass
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");//jmethodID
jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);//CallStatic<Type>Method
//isCopy JNI_FALSE,代表java和c操作的是同一个字符串
char *uuid_str = (*env)->GetStringUTFChars(env, uuid, JNI_FALSE);
}
-----------------------------------------------------------------
//访问构造方法
//使用java.util.Date产生一个当前的时间戳
JNIEXPORT jobject JNICALL Java_com_dongnaoedu_jni_JniTest_accessConstructor
(JNIEnv *env, jobject jobj){
jclass cls = (*env)->FindClass(env, "java/util/Date");
//jmethodID
jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
//实例化一个Date对象
jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);
//调用getTime方法
jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
jlong time = (*env)->CallLongMethod(env, date_obj, mid);
printf("\ntime:%lld\n",time);
return date_obj;
}
//调用父类的方法
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_accessNonvirtualMethod
(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
//获取man属性(对象)
jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/dongnaoedu/jni/Human;");
//获取
jobject human_obj = (*env)->GetObjectField(env, jobj, fid);
//执行sayHi方法
jclass human_cls = (*env)->FindClass(env, "com/dongnaoedu/jni/Human"); //注意:传父类的名称
jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()V");
//执行
//(*env)->CallObjectMethod(env, human_obj, mid);
//调用的父类的方法
(*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid);
}
//中文问题
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_chineseChars
(JNIEnv *env, jobject jobj, jstring in){
char *c_str = "马蓉与宋江";
//char c_str[] = "马蓉与宋喆";
//jstring jstr = (*env)->NewStringUTF(env, c_str);
//执行String(byte bytes[], String charsetName)构造方法需要的条件
//1.jmethodID
//2.byte数组
//3.字符编码jstring
jclass str_cls = (*env)->FindClass(env, "java/lang/String");
jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");
//jbyte -> char
//jbyteArray -> char[]
jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
//byte数组赋值
//0->strlen(c_str),从头到尾
//对等于,从c_str这个字符数组,复制到bytes这个字符数组
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);
//字符编码jstring
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
//调用构造函数,返回编码之后的jstring
return (*env)->NewObject(env,str_cls,constructor_mid,bytes,charsetName);
}
int compare(int *a,int *b){
return (*a) - (*b);
}
//传入
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_giveArray
(JNIEnv *env, jobject jobj, jintArray arr){
//jintArray -> jint指针 -> c int 数组
jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);
//数组的长度
int len = (*env)->GetArrayLength(env, arr);
//排序
qsort(elems, len, sizeof(jint), compare);
//同步
//mode
//0, Java数组进行更新,并且释放C/C++数组
//JNI_ABORT, Java数组不进行更新,但是释放C/C++数组
//JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放)
(*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT);
}
//返回数组
JNIEXPORT jintArray JNICALL Java_com_dongnaoedu_jni_JniTest_getArray(JNIEnv *env, jobject jobj, jint len){
//创建一个指定大小的数组
jintArray jint_arr = (*env)->NewIntArray(env, len);
jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL);
int i = 0;
for (; i < len; i++){
elems[i] = i;
}
//同步
(*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);
return jint_arr;
}
//JNI 引用变量
//引用类型:局部引用和全局引用
//作用:在JNI中告知虚拟机何时回收一个JNI变量
//局部引用,通过DeleteLocalRef手动释放对象
//1.访问一个很大的java对象,使用完之后,还要进行复杂的耗时操作
//2.创建了大量的局部引用,占用了太多的内存,而且这些局部引用跟后面的操作没有关联性
//模拟:循环创建数组
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_localRef(JNIEnv *env, jobject jobj){
int i = 0;
for (; i < 5; i++){
//创建Date对象
jclass cls = (*env)->FindClass(env, "java/util/Date");
jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
jobject obj = (*env)->NewObject(env, cls, constructor_mid);
//此处省略一百行代码...
//不在使用jobject对象了
//通知垃圾回收器回收这些对象
(*env)->DeleteLocalRef(env, obj);
//此处省略一百行代码...
}
}
//全局引用
//共享(可以跨多个线程),手动控制内存使用
jstring global_str;
//创建
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_createGlobalRef(JNIEnv *env, jobject jobj){
jstring obj = (*env)->NewStringUTF(env, "jni development is powerful!");
global_str = (*env)->NewGlobalRef(env, obj);
}
//获得
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_getGlobalRef(JNIEnv *env, jobject jobj){
return global_str;
}
//释放
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_deleteGlobalRef(JNIEnv *env, jobject jobj){
(*env)->DeleteGlobalRef(env, global_str);
}
//弱全局引用
//节省内存,在内存不足时可以是释放所引用的对象
//可以引用一个不常用的对象,如果为NULL,临时创建
//创建:NewWeakGlobalRef,销毁:DeleteGlobalWeakRef
//异常处理
//1.保证Java代码可以运行
//2.补救措施保证C代码继续运行
//JNI自己抛出的异常,在Java层无法被捕捉,只能在C层清空
//用户通过ThrowNew抛出的异常,可以在Java层捕捉
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_exeception(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");
//检测是否发生Java异常
jthrowable exception = (*env)->ExceptionOccurred(env);
if (exception != NULL){
//让Java代码可以继续运行
//清空异常信息
(*env)->ExceptionClear(env);
//补救措施
fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
}
//获取属性的值
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
char *str = (*env)->GetStringUTFChars(env, jstr, NULL);
//对比属性值是否合法
if (_stricmp(str, "super jason") != 0){
//认为抛出异常,给Java层处理
jclass newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
(*env)->ThrowNew(env,newExcCls,"key's value is invalid!");
}
}
//缓存策略
//static jfieldID key_id
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_cached(JNIEnv *env, jobject jobj){
jclass cls = (*env)->GetObjectClass(env, jobj);
//获取jfieldID只获取一次
//局部静态变量
static jfieldID key_id = NULL;
if (key_id == NULL){
key_id = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
printf("--------GetFieldID-------\n");
}
}
//初始化全局变量,动态库加载完成之后,立刻缓存起来
jfieldID key_fid;
jmethodID random_mid;
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_initIds(JNIEnv *env, jclass jcls){
key_fid = (*env)->GetFieldID(env, jcls, "key", "Ljava/lang/String;");
random_mid = (*env)->GetMethodID(env, jcls, "genRandomInt", "(I)I");
}
小结基础:
jclass对象是java中对象所对应的Class对象
jobject对象是java中的对象实体
得到jclass的方式:
1.jclass cls = (*env)->GetObjectClass(env, jobj);
2.jclass cls = (*env)->FindClass(env, "java/util/Date");
得到jobject对象的方式:
jclass cls = (*env)->FindClass(env, "java/util/Date");
jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);//实例化一个Date对象
得到jfieldID方式:
1.jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");//非静态
2.jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");//静态
得到jmethodID方式:
1.jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");//jmethodID
2.jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");//jmethodID
通过jfieldID找到对应的字段值方式:
1.jstring jstr = (*env)->GetObjectField(env, jobj, fid);
2.jint count = (*env)->GetStaticIntField(env, cls, fid); //GetStatic<Type>Field
通过jmethodID找到对应方法的方式:
1.jint random = (*env)->CallIntMethod(env, jobj, mid, 200);//Call<Type>Method
2.jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);//CallStatic<Type>Method
派生类调用父类方法:
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/dongnaoedu/jni/Human;");
jobject human_obj = (*env)->GetObjectField(env, jobj, fid);//得到Human派生类对象
//执行sayHi方法
jclass man_cls = (*env)->FindClass(env, "com/dongnaoedu/jni/Man"); //注意:传父类的名称
jmethodID mid = (*env)->GetMethodID(env, man_cls, "sayHi", "()V");
(*env)->CallNonvirtualObjectMethod(env, human_obj, man_cls, mid);//需要派生类jobject和父类的jclass和父类的jmethodID