授课语音

JNI 实现数据传递

在 Java 和本地代码(C/C++)之间进行数据传递时,JNI(Java Native Interface)提供了一套强大的机制来处理各种类型的数据。通过 JNI,Java 可以调用本地 C/C++ 方法并传递参数,反之,本地代码也可以访问和修改 Java 对象的数据。以下是如何在 JNI 中实现不同类型的数据传递的基本方法。

1. 基本数据类型传递

Java 中声明的 native 方法:

public native int add(int a, int b);

C/C++ 中的实现:

#include <jni.h>

JNIEXPORT jint JNICALL
Java_com_example_MyNativeClass_add(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;  // 返回加法结果
}

解释:

  • jint 对应 Java 的 int 类型。
  • JNIEnv *env 是指向 JNI 环境的指针,用于调用 JNI 提供的函数。
  • jobject obj 是调用该方法的 Java 对象的引用。

2. 传递字符串(String)

在 JNI 中,Java 的 String 类型被表示为 jstring。Java 字符串和 C 字符串的内存布局不同,因此需要使用 JNI 提供的函数来进行转换。

Java 中声明的 native 方法:

public native String getStringFromNative();

C/C++ 中的实现:

#include <jni.h>

JNIEXPORT jstring JNICALL
Java_com_example_MyNativeClass_getStringFromNative(JNIEnv *env, jobject obj) {
    const char *nativeString = "Hello from Native";
    return (*env)->NewStringUTF(env, nativeString);  // 将 C 字符串转化为 Java 字符串
}

解释:

  • (*env)->NewStringUTF(env, nativeString):将 C 字符串 nativeString 转换为 Java 字符串并返回。
  • jstring 是 Java 字符串的 JNI 类型。

3. 传递数组

在 JNI 中,数组的类型有很多种,取决于数组中的元素类型。常见的有:

  • jintArray:Java int[] 数组
  • jdoubleArray:Java double[] 数组
  • jbyteArray:Java byte[] 数组

Java 中声明的 native 方法:

public native int[] multiplyArray(int[] array);

C/C++ 中的实现:

#include <jni.h>

JNIEXPORT jintArray JNICALL
Java_com_example_MyNativeClass_multiplyArray(JNIEnv *env, jobject obj, jintArray array) {
    jsize length = (*env)->GetArrayLength(env, array);  // 获取数组长度
    jint *elements = (*env)->GetIntArrayElements(env, array, NULL);  // 获取数组元素

    // 进行数组元素操作
    for (int i = 0; i < length; i++) {
        elements[i] *= 2;  // 将数组每个元素乘以2
    }

    // 使用原数组元素返回新的数组
    jintArray result = (*env)->NewIntArray(env, length);  // 创建新数组
    (*env)->SetIntArrayRegion(env, result, 0, length, elements);  // 将修改后的数据放入新数组

    // 释放元素引用
    (*env)->ReleaseIntArrayElements(env, array, elements, 0);  // 释放内存

    return result;  // 返回新数组
}

解释:

  • (*env)->GetArrayLength(env, array):获取 Java 数组的长度。
  • (*env)->GetIntArrayElements(env, array, NULL):获取数组的元素并返回指向这些元素的指针。
  • (*env)->NewIntArray(env, length):创建一个新的 int[] 数组。
  • (*env)->SetIntArrayRegion(env, result, 0, length, elements):将修改后的元素设置回新数组。

4. 传递对象

JNI 允许在 Java 和本地代码之间传递对象。通过 jobject 类型,可以访问传递给本地方法的 Java 对象。

Java 中声明的 native 方法:

public native void modifyPerson(Person p);

C/C++ 中的实现:

#include <jni.h>

JNIEXPORT void JNICALL
Java_com_example_MyNativeClass_modifyPerson(JNIEnv *env, jobject obj, jobject person) {
    // 获取 Person 类的 class 引用
    jclass personClass = (*env)->GetObjectClass(env, person);

    // 获取 Person 类中的 name 字段 ID
    jfieldID nameFieldID = (*env)->GetFieldID(env, personClass, "name", "Ljava/lang/String;");
    
    // 获取 name 字段的值
    jstring name = (*env)->GetObjectField(env, person, nameFieldID);

    // 修改 name 字段
    const char *newName = "Updated Name";
    jstring newNameStr = (*env)->NewStringUTF(env, newName);
    (*env)->SetObjectField(env, person, nameFieldID, newNameStr);
}

解释:

  • jobject person:本地代码可以操作传递给它的 Person 对象。
  • (*env)->GetObjectClass(env, person):获取对象的类引用。
  • (*env)->GetFieldID(env, personClass, "name", "Ljava/lang/String;"):获取 Person 类中 name 字段的 ID。
  • (*env)->GetObjectField(env, person, nameFieldID):获取字段的值。
  • (*env)->SetObjectField(env, person, nameFieldID, newNameStr):修改对象的字段值。

5. 异常处理

在 JNI 中,我们可以捕获和处理 Java 异常。通过 JNIEnv,我们可以查询是否发生了异常,并进行相应的处理。

示例:

JNIEXPORT void JNICALL
Java_com_example_MyNativeClass_triggerException(JNIEnv *env, jobject obj) {
    jclass exceptionClass = (*env)->FindClass(env, "java/lang/Exception");
    if (exceptionClass != NULL) {
        (*env)->ThrowNew(env, exceptionClass, "An error occurred in native code");
    }
}

解释:

  • (*env)->FindClass(env, "java/lang/Exception"):查找 Exception 类。
  • (*env)->ThrowNew(env, exceptionClass, "An error occurred in native code"):抛出一个新的 Java 异常。

总结

在 JNI 中,Java 和本地代码之间的数据传递需要使用 JNI 提供的特定数据类型和函数。常见的传递类型包括基本数据类型、字符串、数组、对象等。JNI 通过 JNIEnv 提供了一些接口来访问和修改 Java 对象或数据结构,从而实现 Java 与本地代码之间的数据传递。

去1:1私密咨询

系列课程: