7

我有一个用 C++ 编写的 JNI 函数,它将一个字节数组作为输入,对其进行分段,然后将一个字节数组数组返回给 Java。

JNIEXPORT jobjectArray JNICALL Java_class_method(JNIEnv *env, jobject o, jbyteArray dataToSegment);

在 Java 方面,它很简单:

byte[] arg = getRandomByteArray();
Object[] retVal = x.method(arg);

现在,我发现 JNI 部分有点棘手。我打算创建一个对象数组,每个对象都是一个字节数组。这是因为 JNI 只定义了有限数量的 Java 类型。有 jbyteArray 类型和 jobjectArray 类型,但没有 jarrayOfByteArrays 类型。

所以我创建了我的对象数组,每个对象都被初始化为一个新的字节[1024]:

jobjectArray retVal = env->NewObjectArray(numSegs, env->FindClass("[Ljava/lang/Object;"), env->NewByteArray(1024));

然后我遍历这个数组中的所有索引,执行如下操作:

jbyteArray tmp = (jbyteArray) env->GetObjectArrayElement(retVal, i);
env->SetByteArrayRegion(tmp, 0, size, (jbyte*) sourceBuffer);
env->SetObjectArrayElement(retVal, i, (jobject) tmp); // <--- Questionable line

在大多数情况下,一切都很好。但是,如果我希望每个字节数组都是可变长度的呢?也就是说,我希望字节数组的数组是“锯齿状的”。我将什么作为最后一个参数传递给 NewObjectArray() 作为初始值?我尝试将 0 作为初始值传递以防止在创建 jobjectArray 时进行初始化,然后分配新的 jbyteArray 对象以传递给 SetObjectArrayElement(),但这只会在我每次尝试调用 SetObjectArrayElement 时抛出 ArrayStoreException。实际上,即使将新的 jbyteArray 分配给 tmp 对象(而不是来自 GetObjectArrayElement() 的对象)也会导致在调用 SetObjectArrayElement() 时引发相同的异常。为什么最后一行代码会成为问题?不能用 jbyteArray 作为参数调用 SetObjectArrayElement() 吗?

经过进一步检查,“可疑线路”似乎根本没有做任何事情。当我将其注释掉时,对 tmp 所做的任何更改都会反映在 retVal 中。我知道这是因为 SetByteArrayRegion 调用正在处理“在”jobjectArray 中的字节数组,而不是副本。如果所有行(而是所有一维字节数组)的长度相同,这对我来说就足够了。但他们不是。如何将新的字节数组分配给此对象数组中的一行?

TL;DR:使用 JNI,我有一个 jbyteArrays 的 jobjectArray。如何将其中一个 jbyteArrays 替换为使用 NewByteArray 创建的新的?提示:env->SetObjectArrayElement(retVal, i, (jobject) env->NewByteArray(size)); // 不起作用。

4

2 回答 2

3

我刚刚通过 JNI 成功返回了字节数组数组。初始大小与您的字节数组无关,因为您在填充 jobject 数组时将其替换为新的:

static jbyteArray NewJavaStringBytes(JNIEnv* env, const char *src) {
    jbyteArray retVal = (*env)->NewByteArray(env, strlen(src));
    jbyte *buf = (*env)->GetByteArrayElements(env, retVal, NULL);
    strcpy(buf, src);
    printf("    NewJavaStringBytes: Created java byte array: %s.\n", buf);
    (*env)->ReleaseByteArrayElements(env, retVal, buf, 0);

    return retVal;
}

JNIEXPORT jobjectArray JNICALL Java_TestJniString_printStringArray
  (JNIEnv *env, jobject thisObj, jobjectArray jObjArr) {
    int numStr = (*env)->GetArrayLength(env, jObjArr);
    int idx = 0;
    jobjectArray strArr = NULL;
    jbyte *curStr = NULL;
   jclass arrayElemType = (*env)->FindClass(env, "[B");

    const char *retStrs[] = {"one", "two", "three", "twenty-five", "TESTING!!"};
    const int RET_LEN = sizeof(retStrs) / sizeof(char *);

    printf("Creating java object array of %d items.\n", RET_LEN);
    //Create new array of byte array
    jobjectArray testArray = (*env)->NewObjectArray(env,
                                                    RET_LEN,
                                                    arrayElemType,
                                                    (*env)->NewByteArray(env, 1) );

    for (idx = 0; idx < RET_LEN; ++idx) {   
        printf("Creating java byte array %d from str: %s.\n", idx, retStrs[idx]);
        jbyteArray str = NewJavaStringBytes(env, retStrs[idx]);
        (*env)->SetObjectArrayElement(env, testArray, idx, str);
        (*env)->DeleteLocalRef(env, str);
    }

    printf("printStringArray: Printing %d strings:\n", numStr);
    for (idx = 0; idx < numStr; ++idx) {
        strArr = (*env)->GetObjectArrayElement(env, jObjArr, idx);
        curStr = (*env)->GetByteArrayElements(env, strArr, NULL);
        printf("    %s.\n", curStr);
        (*env)->ReleaseByteArrayElements(env, (jbyteArray)strArr, curStr, 0);
    }

    (*env)->DeleteGlobalRef(env, arrayElemType);

    return testArray;
}

此示例接受一个字节数组数组并返回一个字节数组数组。请注意,这是在 C(不是 C++)中,所以 jni 调用是 (*env)->(env, ...);。在 C++ 中,包装器简化了调用。另外,请注意,这假定 java 代码在发送到本机层之前在字符串的字节数组版本上添加了空终止符。如果您不能指望这一点,那么您必须在 C/C++ 代码中手动添加空项,因为 Java 在从 String 转换时不会为 byte[] 执行此操作。

希望有帮助。

于 2015-02-12T19:09:39.357 回答
2

如果您可以选择使用 JNA 而不是 JNI,您应该能够使用与 JNA API 文档页面中提供的示例类似的内容:

http://jna.java.net/javadoc/overview-summary.html#arrays

“要映射原生多维数组,请使用元素数量相当于完整原生数组的一维 Java 数组”

于 2013-08-06T19:48:56.880 回答