美文网首页
JNI系列入门之C语言与Java的双向通信(二)

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

作者: JerryloveEmily | 来源:发表于2017-03-06 15:29 被阅读192次

JNI系列文章:
JNI系列之入门Hello JNI C(一)
JNI系列之入门Hello JNI C(二)
JNI系列入门之C语言与Java的双向通信(一)
JNI系列入门之C语言与Java的双向通信(二)
JNI系列入门之C语言中文字符串乱码问题


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的时候传入的方法名是固定的<init>。

  • 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系列文章:
JNI系列之入门Hello JNI C(一)
JNI系列之入门Hello JNI C(二)
JNI系列入门之C语言与Java的双向通信(一)
JNI系列入门之C语言与Java的双向通信(二)
JNI系列入门之C语言中文字符串乱码问题

相关文章

网友评论

      本文标题:JNI系列入门之C语言与Java的双向通信(二)

      本文链接:https://www.haomeiwen.com/subject/daxugttx.html