博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JNI基础知识(java中的一套接口,用来跟c和c++通信)
阅读量:4284 次
发布时间:2019-05-27

本文共 11810 字,大约阅读时间需要 39 分钟。

JNI(Java Native Interface),它是java中的一套接口,用来跟c和c++通信。

JNI中的数据类型

java中的数据类型和c的数据类型之间的映射关系:

java->JNI->c/c++

基本数据类型:

java JNI
boolean jboolean  
byte jbyte  
char jchar  
short jshort  
int jlong  
long jchar  
float jfloat  
double jdouble  
void void  

引用类型:

java JNI
String jstring  
object jobject  
class jclass  
byte[] jByteArray  
object[] jobjectArray  

JNI开发流程的步骤

  1. 编写native方法
  2. javah命令,生成.h头文件
  3. 复制.h头文件到CPP工程中
  4. 复制jni.h和jni_md.h文件到CPP工程中
  5. 创建jni目录
  6. 添加本地支持add native support
  7. 实现.h头文件中声明的函数
  8. 生成动态库Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib
  9. 令执行Java程序,加载动态库

java调用C,例如下面返回一个字符串

public class JniTest {    //写一个native方法	public native static String getStringFromC();		public static void main(String[] args) {		String text = getStringFromC();		System.out.println(text);	}	//加载动态库	static{		System.loadLibrary("jni_study");	}}

C中

//函数实现//Java_包名_类名_方法名JNIEXPORT jstring JNICALL Java_com_chs_jni_JniTest_getStringFromC(JNIEnv *env, jclass jcls){	//JNIEnv 结构体指针  env是二级指针	//代表Java运行环境,调用Java中的代码	//将C的字符串转为一个java字符串	return (*env)->NewStringUTF(env,"C String");}

C调用java中的方法

访问属性,访问静态属性,访问方法,访问静态方法

先在java中定义相关的属性和方法

public class JniTest {	//加载动态库	static{			System.loadLibrary("jni_test");	}	public String name = "chs";		public static int count = 9;		public native static String getStringFromC();		public native String getString2FromC(int i);	//访问属性,返回修改之后的属性内容	public native String accessField();	//c调用java中的静态变量	public native void accessStaticField();	//c调用java中的方法	public native void accessMethod();	//c调用java中的静态方法	public native void accessStaticMethod();	//c调用java中的构造方法	public native void accessConstructor();	//c调用java中的父类的方法	public native void accessNonvirtualObjectMethod();	//中文	public native String chuneseChars(String in)	//传入数组	public native void giveArray(int[] array);	//获取数组	public native int[] getArray(int len);	//获取本地引用	public native void loaclRef();		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);				t.accessField();		t.accessStaticField();		t.accessMethod();		t.accessStaticMethod();		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);		}		//全局引用		t.createGlobalRef();		System.out.println(t.getGlobalRef());		//用完之后释放		t.deleteGlobalRef();		System.out.println("释放完了...");		//System.out.println(t.getGlobalRef());		//Java中捕捉C中的异常Exception是无法捕捉到的		//得用Throwable捕获		try {			t.exeception();								} catch (Exception e) {			System.out.println("发生异常:"+e.getMessage());		}		System.out.println("--------异常发生之后-------");		//C中手动抛出来的异常可以捕捉到		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){		return new Random().nextInt(max); 	}		//产生UUID字符串	public static String getUUID(){		return UUID.randomUUID().toString();	}

1.访问属性

这里会用到属性签名

数据类型 签名
boolean Z  
byte B  
char C  
short S  
int I  
long L  
float F  
double D  
void V  
object L开头,然后以/分割包的完整类型,后面再加; 
比如String的签名就是Ljava/long/String
 
Array 以[开头,在加上数组元素类型的签名, 
比如int[],签名是[I,int[][]的签名是[[I, 
object[]的签名是[Ljava/lang/Object
 
JNIEXPORT jstring JNICALL Java_com_chs_JniTest_accessField(JNIEnv *env, jobject jobj){	//jobj是t对象,JniTest.class	jclass cls = (*env)->GetObjectClass(env, jobj);	//jfieldID	//属性名称,属性签名	jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");		//jason >> super jason	//获取key属性的值	//Get
Field jstring jstr = (*env)->GetObjectField(env, jobj, fid); printf("jstr:%#x\n",&jstr); //jstring -> c字符串 //isCopy 是否复制(true代表赋值,false不复制) char *c_str = (*env)->GetStringUTFChars(env,jstr,JNI_FALSE); //拼接得到新的字符串 char text[20] = "super "; strcat(text,c_str); //c字符串 ->jstring jstring new_jstr = (*env)->NewStringUTF(env, text); //修改key //Set
Field (*env)->SetObjectField(env, jobj, fid, new_jstr); printf("new_jstr:%#x\n", &new_jstr); return new_jstr;}

2.访问静态属性

JNIEXPORT void JNICALL Java_com_chs_JniTest_accessStaticField(JNIEnv *env, jobject jobj){	//jclass	jclass cls = (*env)->GetObjectClass(env, jobj);	//jfieldID	jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");	//GetStatic
Field jint count = (*env)->GetStaticIntField(env, cls, fid); count++; //修改 //SetStatic
Field (*env)->SetStaticIntField(env,cls,fid,count);}

3.访问java方法

这里需要用到方法的签名,方法的签名的获取,可以通过javap命令,打开命令行,进入我们的java类所在的文件夹,执行javap命令 比如

javap -s -p com.chs.JniTest

执行上面的命令,就可以看到该类下面所有属性和方法的签名了。

JNIEXPORT void JNICALL Java_com_chs_JniTest_accessMethod(JNIEnv *env, jobject jobj){	//jclass	jclass cls = (*env)->GetObjectClass(env, jobj);	//jmethodID  最后一个参数是方法的签名	jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");	//Call
Method jint random = (*env)->CallIntMethod(env, jobj, mid, 200); printf("random num:%ld",random);

4.访问静态方法

JNIEXPORT void JNICALL Java_com_chs_JniTest_accessStaticMethod(JNIEnv *env, jobject jobj){	//jclass	jclass cls = (*env)->GetObjectClass(env, jobj);	//jmethodID		jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");		//CallStatic
Method jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid); //随机文件名称 uuid.txt //jstring -> char* //第三个参数isCopy JNI_FALSE,代表java和c操作的是同一个字符串 char *uuid_str = (*env)->GetStringUTFChars(env, uuid, NULL); //拼接 char filename[100]; sprintf(filename, "D://%s.txt",uuid_str); FILE *fp = fopen(filename,"w"); fputs("i love jason", fp); fclose(fp);}

5.访问构造方法

访问构造方式的时候需要传入一个” “参数

//使用java.util.Date产生一个当前的时间戳JNIEXPORT jobject JNICALL Java_com_chs_JniTest_accessConstructor(JNIEnv *env, jobject jobj){    //找到要访问的class	jclass cls = (*env)->FindClass(env, "java/util/Date");	//找到它的构造方法	jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "
", "()V"); //实例化一个Date对象 jobject date_obj = (*env)->NewObject(env, cls, constructor_mid); //找到想要调用的方法 jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J"); //调用getTime方法 jlong time = (*env)->CallLongMethod(env, date_obj, mid); printf("\ntime:%lld\n",time); return date_obj;}

6.调用父类的方法

Java中定义一个父类Human,和一个子类Man,父类中有个方法sayHai,子类重写该方法。

java中

Human human = new Man();

C中

JNIEXPORT void JNICALL Java_com_chs_JniTest_accessNonvirtualMethod(JNIEnv *env, jobject jobj){    //拿到该对象	jclass cls = (*env)->GetObjectClass(env, jobj);	//找到属性的ID	jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/chs/Human;");	//获取man属性(对象	jobject human_obj = (*env)->GetObjectField(env, jobj, fid);	//知道父类,注意:传父类的名称	jclass human_cls = (*env)->FindClass(env, "com/chs/Human"); 	//执行sayHi方法	jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()V");	//执行CallObjectMethod,还是会调用子类的方法	//(*env)->CallObjectMethod(env, human_obj, mid);	//使用CallNonvirtualObjectMethod可以调用的父类的方法	(*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid);}

7.从C中返回中文乱码问题

C中字符串默认是UTF-16,想要返回到Java中不乱码,需要进行转码,C中转码非常麻烦,通过C调用Java中的String提供的类来进行转码就简单了很多。

JNIEXPORT jstring JNICALL Java_com_chs_JniTest_chineseChars(JNIEnv *env, jobject jobj, jstring in){	//输出	//char *c_str = (*env)->GetStringUTFChars(env, in, NULL);	//printf("%s\n",c_str);	//c 转化成 jstring	char *c_str = "我是C中的文字";	//char c_str[] = "我是C中的文字";	//jstring jstr = (*env)->NewStringUTF(env, c_str);	//执行String(byte bytes[], String charsetName)构造方法需要的条件	//1.jmethodID	//2.byte数组	//3.字符编码jstring	    //找到String类,并回去构造方法的ID	jclass str_cls = (*env)->FindClass(env, "java/lang/String");	jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "
", "([BLjava/lang/String;)V"); //jbyte 转换成 char //jbyteArray -> char[] jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str)); //byte数组赋值,C中的char跟jbyte类型是一样的 //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);}

8.Java传数组到C中

传入一个数组并排序,操作完一定要同步回去

//比较的方法int compare(int *a,int *b){	return (*a) - (*b);}//传入JNIEXPORT void JNICALL Java_com_chs_JniTest_giveArray(JNIEnv *env, jobject jobj, jintArray arr){	//jintArray -> jint指针 -> c int 数组	jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);	//printf("%#x,%#x\n", &elems, &arr);	//数组的长度	int len = (*env)->GetArrayLength(env, arr);	//排序	qsort(elems, len, sizeof(jint), compare);		//同步	//0, Java数组进行更新,并且释放C/C++数组	//JNI_ABORT, Java数组不进行更新,但是释放C/C++数组	//JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放)	(*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT);}

9.C返回数组到Java

操作完一定要同步回去

JNIEXPORT jintArray JNICALL Java_com_chs_JniTest_getArray(JNIEnv *env, jobject jobj, jint len){	//创建一个指定大小的数组	jintArray jint_arr = (*env)->NewIntArray(env, len);	//创建jint数组	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;}

10.JNI中的引用

引用类型分为局部引用和全局引用

局部引用可以使用DeleteLocalRef手动释放对象,什么时候释放呢

  1. 当访问一个恨到的java对象,使用完成知乎,还要进行复杂的耗时操作
  2. 当创建了大量的局部引用,占用了太多的内存,这些局部引用只是暂时使用,跟后面的操作没有关联。

局部引用:

JNIEXPORT void JNICALL Java_com_chs_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, "
", "()V"); jobject obj = (*env)->NewObject(env, cls, constructor_mid); //省略一些操作 //不在使用jobject对象了 //通知垃圾回收器回收这些对象 (*env)->DeleteLocalRef(env, obj); //省略一些操作... }}

全局引用

全局引用可以共享数据,可以跨多个线程,使用DeleteGlobalRef释放。

jstring global_str;//创建全局引用JNIEXPORT void JNICALL Java_com_chs_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_chs_JniTest_getGlobalRef(JNIEnv *env, jobject jobj){	return global_str;}//释放全局引用JNIEXPORT void JNICALL Java_com_chs_JniTest_deleteGlobalRef(JNIEnv *env, jobject jobj){	(*env)->DeleteGlobalRef(env, global_str);}

弱全局引用

全局引用需要手动释放,弱全局引用,跟java中的弱引用差不多,当内存不足的时候系统会释放掉这部分内存。

创建:NewWeakGlobalRef,销毁:DeleteGlobalWeakRef

11.C中异常处理

ExceptionOccurred
JNIEXPORT void JNICALL Java_com_chs_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!");	}}

12.缓存策略

如果从java中循环调用很多次下面的方法,我们让对象第一次拿到之后就缓存起来,以后再取的时候就去缓存中取,这时候可以使用局部静态变量来存储。

在实际的项目中,我们也可以在一个方法中一次初始化很多需要的全局的变量。

JNIEXPORT void JNICALL Java_com_chs_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_chs_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");}

转载地址:http://dyngi.baihongyu.com/

你可能感兴趣的文章
Linux 学习笔记 (九) 软件安装
查看>>
Linux 学习笔记 (十) shell 脚本初探
查看>>
关于移动开发的一些思考
查看>>
Java 进阶 -- final 解析
查看>>
VS 2013 Professional 安装问题
查看>>
Xamarin.Forms.Xaml.XamlParseException: No embeddedresource found for
查看>>
error: Mono.Android could not be found
查看>>
使用 RDCMan
查看>>
Xamarin 实现 Button LongClick 和 Touch
查看>>
Xamarin 进阶文档
查看>>
在 Xamarin Forms 中实现 Banner
查看>>
Android 加载本地图片路径
查看>>
Android CPU 架构详解
查看>>
Android
查看>>
Android UncaughtExceptionHandler 原理分析
查看>>
Linker 分析器
查看>>
Android APP 检测安装打开 APK 三步操作
查看>>
Xamarin.Forms Performance on Android
查看>>
AndroidManifest.xml <uses-feature> 和 <uses-permisstion>
查看>>
No toolchains found in the NDK toolchains folder for ABI with prefix: mipsel-linux-android
查看>>