JNI访问Java方法

174 阅读4分钟

上一篇我们学习了《JNI数据类型和属性方法》,了解了JNI和Java数据类型的关系,以及JNI访问JAVA类的一些方法,包括静态方法,接下来我们继续学习。

访问构造方法

JAVA类定义native方法

public class Test {

	
	public native Data accessConstructor();
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test t = new Test();
		Data mData = t.accessConstructor();
		
		DateFormat dateFormat = new SimpleDateFormat("EE MM-dd-yyyy");
        System.out.println(dateFormat.format(mData));
		
		System.out.println( mData.toString());
	}
	
	
	//加载动态库
	static {
		System.loadLibrary("ConsoleApplication1");
	}

}

按照一般的步骤,需要生成com.lwj.test.Test 类的头文件com_lwj_test_Test.h

这里演示的是:使用java.util.Date产生一个当前时间时间戳,并且将Date对象返回给Java类的accessConstructor方法。

//实现方法
JNIEXPORT jobject JNICALL Java_com_lwj_test_Test_accessConstructor
(JNIEnv *env, jobject jobj){
	//jclass
	jclass cls = (*env)->FindClass(env, "java/util/Date");
	//构造方法jmethodID
	jmethodID mid = (*env)->GetMethodID(env,cls,"<init>","()V");
	//实例化一个Date对象
	jobject obj = (*env)->NewObject(env,cls,mid);
	//调用getTime方法
	jmethodID t_mid = (*env)->GetMethodID(env,cls,"getTime","()J");
	jlong time = (*env)->CallLongMethod(env,obj,t_mid);
	printf("time long:%lld",time);
	//jlong -> 字符串
	FILE *fp = fopen("D://log.txt", "w");
	char str[50];
	sprintf(str, "%lld", time);//打印到字符串中
	fputs(str, fp);//将字符串写入文件
	fclose(fp);

	return obj;

}

生成动态库(.dll)文件,之后在Java中调用得到结果

运行结果.png

调用父类的方法

Java类:

public class Animal {
	
	public void walk() {
		System.out.println("动物都会走路~");
	}

}
public class Monkey extends Animal{
	
	@Override
	public void walk() {
		// TODO Auto-generated method stub
//		super.walk();
		System.out.println("猴子会爬树~");
	}
}
public class Test {

	private Animal mMonkey = new Monkey();
	public native void callSuperMethod();
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test t = new Test();
		t.callSuperMethod();
	}
		
	
	//加载动态库
	static {
		System.loadLibrary("ConsoleApplication1");
	}

}

JNI调用

JNIEXPORT void JNICALL Java_com_lwj_test_Test_callSuperMethod
(JNIEnv *env, jobject jobj){
	
	jclass jcls = (*env)->GetObjectClass(env,jobj);

	jfieldID fid = (*env)->GetFieldID(env,jcls,"mMonkey"," Lcom/lwj/test/Animal;");

	jobject super_obj = (*env)->GetObjectField(env, jobj, fid);

	//执行walk方法
	jclass super_cls = (*env)->FindClass(env,"com/lwj/test/Animal");//注意:传父类的类名
	jmethodID mid = (*env)->GetMethodID(env, super_cls, "walk", "()V");

	//执行子类覆盖的方法
	//(*env)->CallObjectMethod(env, super_obj, mid);
	//执行父类的方法

	(*env)->CallNonvirtualVoidMethod(env, super_obj, super_cls, mid);

}

运行结果.png

解决中文字符串乱码

C字符串->JSTRING
一般的从JNI返回中文字符串会出现乱码的情况

public class Test {

	
	public native String chineseChars(String text);
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test t = new Test();
		System.out.println(t.chineseChars("是时候了"));
	}
		
	
	//加载动态库
	static {
		System.loadLibrary("ConsoleApplication1");
	}

}

编写native方法

JNIEXPORT jstring JNICALL Java_com_lwj_test_Test_chineseChars
(JNIEnv *env, jobject jobj, jstring jstr){

	char *cstr = "我叫小明";
	return (*env)->NewStringUTF(env,cstr);
}

运行结果.png

以上的JNI实现就是直接返回一个jstring,出现了乱码。

针对这个情况,在实现native函数中进行处理

JNIEXPORT jstring JNICALL Java_com_lwj_test_Test_chineseChars
(JNIEnv *env, jobject jobj, jstring jstr){

	char *cstr = "我叫小明";

	//String类的jclass
	jclass str_cls = (*env)->FindClass(env, "java/lang/String");
	jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V");

	//char * -> char[] ->jbyteArray -> jbyte * 
	jbyteArray bytes = (*env)->NewByteArray(env, strlen(cstr));
	//bytes数组赋值
	(*env)->SetByteArrayRegion(env, bytes, 0, strlen(cstr), cstr);

	//charsetName="GB2312"
	jstring charsetName = (*env)->NewStringUTF(env, "GB2312");

	//返回GB2312中文编码jstring
	return (*env)->NewObject(env, str_cls, constructor_mid, bytes, charsetName);
}

这里借用Java的转码API,返回GB2312中文编码字符串,使用java.lang.string的构造函数public String(byte bytes[], String charsetName)完成转码。

执行这个构造方法需要三个东西 1))jmethodID 2)byte数组(jbyteArray)参数 3)charsetName参数(jstring)

最后运行的结果如下:

运行结果.png

数组处理

演示:传入int数组,对数组进行排序

Java类

public class Test {

	
	public native void giveArray(int[] array);
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] array = {89,100,34,2,10,5};
		Test t = new Test();
		t.giveArray(array);
		for (int i : array) {
			System.out.println(i);
		}
	}
		
	
	//加载动态库
	static {
		System.loadLibrary("ConsoleApplication1");
	}

}

实现native方法


int compare(int *a, int *b){
	return (*b) - (*a);
}

JNIEXPORT jstring JNICALL Java_com_lwj_test_Test_giveArray
(JNIEnv * env, jobject obj, jintArray arr){
	//Java的int数组(jintArray)->C int数组
	int *elems = (*env)->GetIntArrayElements(env,arr,NULL);
	//数组的长度
	int len = (*env)->GetArrayLength(env,arr);
	//对(jint)long数组进行
	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);
}

说明:对于java传值到Jni实现函数 1、如果是java的基本数据类型,传过来的就是值 2、如果是java的引用类型(对象),传的就是引用 在这里 jintArray 是引用类型,是指向Java数组的指针。

最后需要使用ReleaseIntArrayElementsarr 和 elems 进行同步。

返回指定大小的数组

public class Test {

	
	public native int[] getArray(int size);
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test t = new Test();
		int[] array = t.getArray(10);
		for (int i : array) {
			System.out.println(i);
		}
	}
		
	
	//加载动态库
	static {
		System.loadLibrary("ConsoleApplication1");
	}

}

实现native方法

JNIEXPORT jstring JNICALL Java_com_lwj_test_Test_getArray
(JNIEnv * env, jobject obj, jint size){
	jintArray arrs = (*env)->NewIntArray(env,size);
	//赋值
	//jintArray -> jint *
	jint *elems = (*env)->GetIntArrayElements(env,arrs,NULL);
	int i = 0;
	//循环赋值
	for (; i < size; i++){
		elems[i] = i;
	}
	//同步
	(*env)->ReleaseIntArrayElements(env, arrs, elems, 0);
	return arrs;
}

运行结果.png