JNI系列入门之C语言与Java的双向通信(二)

1,594 阅读4分钟

JNI系列文章:

  1. JNI系列之入门Hello JNI C(一)
  2. JNI系列之入门Hello JNI C(二)
  3. JNI系列入门之C语言与Java的双向通信(一)
  4. JNI系列入门之C语言与Java的双向通信(二)
  5. JNI系列入门之C语言中文字符串乱码问题

Jerry哥人狠话不多,直接讲正题。

C层向Java层通信

  • C层访问Java层的方法
// java代码
/*
* 在C中调用次方法,获取登入的用户id
*/
private String getLoginUserId(){
    return "100010";
}
// c代码
// 3. 访问java方法
JNIEXPORT void JNICALL Java_com_jerry_jnitest_JniTest_accessMethod
(JNIEnv *env, jobject jobj) {
	// 获取jclass
	jclass cls = (*env)->GetObjectClass(env, jobj);
    // 获取调用方法的jmethodID
	jmethodID methodID = (*env)->GetMethodID(env, cls, "getLoginUserId", "()Ljava/lang/String;");
    // 调用获取登录用户id的方法 
	jint value = (*env)->CallIntMethod(env, jobj, methodID);
	printf("userId = %s\n", value);
}
  • C层访问Java层的静态方法
// java代码
// c中调用的java静态方法
/**
  * 获取文件的大小
  * @param pathName
  * @return 文件大小
  */
  public static long getFileSize(String pathName){
	  return new File(pathName).length();
  }
// 创建和写入一个字符串到文件中,并返回文件大小
JNIEXPORT jlong JNICALL Java_com_jerry_jnitest_JniTest_createAndWriteFile
(JNIEnv *env, jobject jobj, jstring jstr_file_path) {
	// 写入的文件路径 jstring->c
	char *filename = (*env)->GetStringUTFChars(env, jstr_file_path, NULL);
	printf("filename: %s\n", filename);
	// 创建一个文件, "w"表示写入权限,文件存在则覆盖
	FILE *fp = fopen(filename, "w");
	char *text = "在C中创建一个文件并写入内容,并返回文件大小";
	// c的文件io函数,写入文件
	fputs(text, fp);
	// 关闭流
	fclose(fp);

	// 计算文件的长度,这里不用c的函数来计算,改用调用java的文件api的方式来计算
	// 获取getFileSize方法所在类的类类型
	jclass jcls = (*env)->GetObjectClass(env, jobj);
	// 获取方法的id
	jmethodID mid = (*env)->GetStaticMethodID(env, jcls, "getFileSize",
		"(Ljava/lang/String;)J");
	// 调用方法
	jlong file_size = (*env)->CallStaticLongMethod(env, jcls, mid, jstr_file_path);
	printf("file_size: %lld", file_size);

	// 释放filename
	(*env)->ReleaseStringUTFChars(env, jstr_file_path, filename);
	return file_size;
}

这里我通过C调用了java的文件计算api,因为java计算文件比较简单,直接File.length()就好了。当然也可以用c来实现获取文件的长度大小:

// 4. 获取文件大小
int filesize(FILE *fp) {
	int length = 0;
	if (fp == NULL) {
		return length;
	}
	// 将文件指针的位置,重新定位文件指针
	// 0是偏移量,SEEK_END表示文件的末尾位置
	fseek(fp, 0, SEEK_END);
	// 返回当前文件指针,相对于文件开头的位置偏移量,就是文件字节长度
	length = ftell(fp);
	printf("length = %d\n", length);
	return length;
}

小伙伴们肯定会有疑问,你这方法的签名,记不住啊,容易懵逼啊。没有关系,我们还可以用命令的方式生成:

生成java的方法签名
javap -s -p 类的完整名称,可以自动生成属性和方法签名。

  • C层访问Java层的构造方法,并创建Java对象返回
// 5. 访问java的构造方法
// 使用java.util.Date获得一个时间戳
JNIEXPORT jobject JNICALL Java_com_jerry_jnitest_JniTest_accessConstructor
(JNIEnv *env, jobject jobj) {
	// 获取jclass
	jclass cls = (*env)->FindClass(env, "java/util/Date");
	// 获取jmethodID, 构造方法的名字使用<init>
	jmethodID contructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
	// 调用构造方法,实例化一个Date对象
	jobject date_jobj = (*env)->NewObject(env, cls, contructor_mid);

	// 调用Date的getTime方法
	// 获取date的jclass
	jclass date_cls = (*env)->GetObjectClass(env, date_jobj);
	// 获取getTime的jmethodID
	jmethodID getTime_mid = (*env)->GetMethodID(env, date_cls, "getTime", "()J");
	// 调用getTime方法
	jlong timestamp_jlong = (*env)->CallLongMethod(env, date_jobj, getTime_mid);
	printf("timestamp = %lld\n", timestamp_jlong);
	return date_jobj;
}

构造方法比较特殊,在获取jmethodID的时候传入的方法名是固定的。

  • java中传入数组
// 8. java传入一个数组进行排序后同步

// 比较两个数的大小
int compare(int *a, int *b) {
	return (*a) - (*b);
}

JNIEXPORT void JNICALL Java_com_jerry_jnitest_JniTest_inputSortArray
(JNIEnv *env, jobject jobj, jintArray jintArr) {
	// 获取数组中的元素jint*
	jint *int_arr = (*env)->GetIntArrayElements(env, jintArr, NULL);
	// 获取数组长度
	int length = (*env)->GetArrayLength(env, jintArr);
	// 利用快速排序法排列数组
	qsort(int_arr, length, sizeof(jint), compare);

	// 同步操作后的数组内存到java中
	// 最后一个参数mode的解释
	// 0:Java的数组更新同步,然后释放C/C++的数组内存
	// JNI_ABORT:Java的数组不会更新同步,但是释放C/C++的数组内存
	// JNI_COMMIT:Java的数组更新同步,不释放C/C++的数组内存(但是函数执行完了局部的变量还是会释放掉)
	(*env)->ReleaseIntArrayElements(env, jintArr, int_arr, 0);
}

ReleaseIntArrayElements这个函数很重要,只有调用了这个函数,C中操作的数组元素,才会同步到java,否则java中传入的数组打印后还是原来的。

  • C中生成一个数组返回给java
// 9. C中生成一个数组返回给java
JNIEXPORT jintArray JNICALL Java_com_jerry_jnitest_JniTest_outputArray
(JNIEnv *env, jobject jobj, jint len) {
	// 创建一个jintArray数组变量
	jintArray jint_Arr = (*env)->NewIntArray(env, len);
	// 将jintArray转换成c的jint*指针进行数组赋值
	jint *elements = (*env)->GetIntArrayElements(env, jint_Arr, NULL);
	int i = 0;
	for (; i < len; i++) {
		elements[i] = i;
	}
	// 将C中的创建修改的数组同步到Java中
	(*env)->ReleaseIntArrayElements(env, jint_Arr, elements, 0);
	return jint_Arr;
}

同样也要调用ReleaseIntArrayElements函数,去同步操作的数据,java获取的数组才会有值,同时释放掉C/C++的数组内存。


JNI系列文章:

  1. JNI系列之入门Hello JNI C(一)
  2. JNI系列之入门Hello JNI C(二)
  3. JNI系列入门之C语言与Java的双向通信(一)
  4. JNI系列入门之C语言与Java的双向通信(二)
  5. JNI系列入门之C语言中文字符串乱码问题