7

我的应用需要使用 jni。逻辑看起来像:

void myJniFunc(JNIEnv *env, jclass clazz, jobjectArray items) {
    int count = 10;
    struct MyObj *myObjArray = (struct MyObj*)malloc(sizeof(struct MyObj) * count);
    for (i = 0; i < count; i++) {
        jobject obj = (*env)->GetObjectArrayElement(env, items, i);
        jfieldID fieldId = ...;
        jstring jstr = (*env)->GetObjectField(env, obj, fieldId);
        myObjArray[i].name = (*env)->GetStringUTFChars(env, jstr);
        (*env)->DeleteLocalRef(env, obj);
        // Location A
    }

    // some code which will use myObjArray
    process(count, myObjectArray);

    // Location B
}

并且通过 JNI doc,GetStringUTFChars 返回的数组应该使用 relased

(*env)->ReleaseStringUTFChars(env, jstr, myObjArray[i].name);
(*env)->ReleaseLocalRef(env, jstr);
  1. 如果我在位置 A 释放返回的数组,则 myObjArray.name 将为空
  2. 如果我在位置 B 释放返回的数组,因为我将保留 jstring 的引用,那么“添加到 JNI 本地引用表失败(有 512 个条目)”将发生

我的问题是:如果我想正确释放 jstring,我该怎么办?

4

3 回答 3

7

由于您的循环正在创建本地引用 (GetObjectField),因此您需要在循环中释放它 (DeleteLocalRef),否则您将遇到本地引用的限制。您必须在两次调用之间完全处理 Java 字符串。

由于您希望将字符串的字节保留在循环之外使用,因此您需要复制字节,因为必须在释放字符串引用之前释放 JVM 的固定(或临时副本)(GetStringUTFChars)(ReleaseStringUTFChars)。

所以循环内字符串的顺序必须是:

  1. GetObjectField
  2. GetStringUTFChars
  3. 制作自己的副本
  4. ReleaseStringUTFChars
  5. DeleteLocalRef

注意:GetStringUTFChars您将获得一个指向 Java 字符串的修改后的 UTF-8 编码的指针。这里有两点:

  1. 您的代码应该能够处理修改后的 UTF-8 编码字符。(它每个字符有 1 到 6 个字节,并以一种特殊的方式对 NUL 进行编码。)
  2. 文档没有说明数组是否以 0 结尾。您可以使用GetStringUTFLength来获取修改后的 UTF-8 编码中的字节数——不包括任何 0 终止符。(各种 JNI 实现和The Book都同意数组是 0 终止的。)如果您想使用终止符制作自己的副本,请务必为终止符添加空间。

如果您更愿意使用 UTF-16 编码,请使用GetStringCharsand GetStringLength。在这种情况下,数组肯定没有终止;它使用内部计数和字符串字节而不进行任何转换。

或者,如果您想更改字符集,例如真正的“UTF-8”、“ASCII”、“CP437”或“Windows-1252”或您的代码可以处理的其他内容,请使用String.getBytes重载或Charset类。Charset如果您想控制如何处理目标字符集中不支持的字符,请使用该类。

于 2013-07-08T14:42:28.470 回答
0

经过一些实验,我得到了答案,但我不确定它是否完全正确。我在循环中删除了 jstring 的引用,错误“添加到 JNI 本地引用表(有 512 个条目)失败”不再发生。

void myJniFunc(JNIEnv *env, jclass clazz, jobjectArray items) {
    int count = 10;
    jstring tempArray[count];
    struct MyObj *myObjArray = (struct MyObj*)malloc(sizeof(struct MyObj) * count);
    for (i = 0; i < count; i++) {
        jobject obj = (*env)->GetObjectArrayElement(env, items, i);
        jfieldID fieldId = ...;
        jstring jstr = (*env)->GetObjectField(env, obj, fieldId);
        myObjArray[i].name = (*env)->GetStringUTFChars(env, jstr);
        (*env)->DeleteLocalRef(env, obj);

        // Location A
        tempArray[i] = jstr;
        (*env)->DeleteLocalRef(jstr);
    }

    // some code which will use myObjArray
    process(count, myObjectArray);

    // Location B
    for (i = 0; i < count; i++) {
        (*env)->ReleaseStringUTFChars(env, tempArray[i], myObjectArray[i].name);
    }
}

我担心的是:函数“ReleaseStringUTFChars”的第二个参数需要是一个 jstring。所以我创建了一个数组来保存 jstring 的引用以供以后发布。当我在循环中删除 jstring 的引用时,这意味着 jstring 被释放。我在这里对那个 jstring 调用“ReleaseStringUTFChars”有什么问题吗?通过我的测试,我没有遇到任何问题。

于 2013-07-08T07:07:41.850 回答
0

除了 Tom Blodget 的帖子,我还可以添加:不要在 jstring 对象上使用 DeleteLocalRef 方法,并且不要首先将 java 字符串从 jobject 转换为 jstring(例如,当您从 java 方法中检索字符串时)!将其保留在 jobject 类型引用中,并且仅当您将其作为参数传递时将其转换为 jstring,否则您会收到“JNI 错误(应用程序错误):访问过时的本地”错误!例如,不要做这样的事情:

jstring imeiObj = (jstring)env->CallObjectMethod(telephonyManagerObj, jniInfo->jniCache.telephonyManagerClass_getDeviceIdMethod, i);
const char* imeiStr = env->GetStringUTFChars(imeiObj, &isCopy);
env->DeleteLocalRef((jobject)imeiObj);

最好这样做:

jobject imeiObj = env->CallObjectMethod(telephonyManagerObj, jniInfo->jniCache.telephonyManagerClass_getDeviceIdMethod, i);
const char* imeiStr = env->GetStringUTFChars((jstring)imeiObj, &isCopy);
env->DeleteLocalRef(imeiObj);

我不知道为什么会这样

于 2020-02-05T10:44:37.347 回答