在jni中使用字符串和数组(摘录)

    本文摘自 http://java6ean.iteye.com/blog/518228 中的jni中文翻译,以处理在常规的jni应用中(通常是与硬件api)进行交互时,处理如何通过jni调用简单的c或c++底层api。
    通常来说,使用部分硬件api时,调用的api接口都相对简单,只需要访问由硬件方提供的几个api dll即可,但由于通常api都是通过c或c++来写的,只提供了能够调用底层的api实现,并没有提供高级语言的实现接口,如java,c#。这时候,在java项目中就需要自己编写jni了。现在虽然有一些jni技术,如sun(oracle)的jna,com4j以及jacob等,但如果想要正确的访问,还是需要自己来编写相应的实现,以直接操纵自己写的api,以直接访问内存,避免由于第三方的转译等,发生不必要的错误。

    使用对应的 JNI 函数把 jstring 转成 C/C++字串。JNI 支持 Unicode/UTF-8 字符编码互转。Unicode 以 16-bits 值编码;UTF-8 是一种以字节为单位变长格式的字符编码,并与 7-bits。ASCII 码兼容。UTF-8 字串与 C 字串一样,以 NULL('\0')做结束符, 当 UTF-8 包含非 ASCII码字符时,以'\0'做结束符的规则不变。7-bit ASCII 字符的取值范围在 1-127 之间,这些字符的值域与 UTF-8 中相同。当最高位被设置时,表示多字节编码。如下,调用 GetStringUTFChars,把一个 Unicode 字串转成 UTF-8 格式字串,如果你确定字串只包含 7-bit ASCII 字符。这个字串可以使用 C 库中的相关函数,如 printf.
    如下一个简单的接受由控制台输入并转换为java string的例子

Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt){
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s", str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than
* 127 characters */
scanf("%127s", buf);
return (*env)->NewStringUTF(env, buf);
}

    记得检测 GetStringUTFChars 的返回值,因为调用该函数会有内存分配操作,失败后,该
函数返回 NULL,并抛 OutOfMemoryError 异常。发生异常,不会改变代码执行轨迹,所以,当返回 NULL,要及时返回,或马上处理异常。

    除了 GetStringUTFChars, ReleaseStringUTFChars, 和 NewStringUTF, JNI 还支持其他操作 String 的函数供使用。
    GetStringChars 是有 Java 内部 Unicode 到本地 UTF-8 的转换函数,可以调用GetStringLength,获得以 Unicode 编码的字串长度。也可以使用 strlen 计算GetStringUTFChars 的返回值,得到字串长度。
    const jchar * GetStringChars(JNIEnv *env, jstring str, jboolean *isCopy);
    上述声明中,有 isCopy 参数,当该值为 JNI_TRUE,将返回 str 的一个拷贝;为JNI_FALSE 将直接指向 str 的内容。 注意:当 isCopy 为 JNI_FALSE,不要修改返回值,不然将改变 java.lang.String 的不可变语义。一般会把 isCopy 设为 NULL,不关心 Java VM 对返回的指针是否直接指向
java.lang.String 的内容。
    记住在调用 GetStringChars 之后,要调用 ReleaseStringChars 做释放,不管在调用GetStringChars 时为 isCopy 赋值 JNI_TRUE 还是 JNI_FALSE,因不同 JavaVM 实现的原因,ReleaseStringChars 可能释放内存,也可能释放一个内存占用标记(isCopy 参数的作用,从GetStringChars 返回一个指针,该指针直接指向 String 的内容,为了避免该指针指向的内容被 GC,要对该内存做锁定标记)。

    GetStringRegion/GetStringUTFRegion,向准备好的缓冲区赋值,如下:

JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt){
/*assumethepromptstringanduserinputhaslessthan128
characters */
char outbuf[128], inbuf[128];
int len = (*env)->GetStringLength(env, prompt);
(*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf);
printf("%s", outbuf);
scanf("%s", inbuf);
return (*env)->NewStringUTF(env, inbuf);
}

    GetStringUTFRegion 有两个参数,starting index 和 length, 这两个参数以 Unicode 编码计算. 该函数会做边界检查,所以可能抛出 StringIndexOutOfBoundsException。因为该函数不涉及内存操作,所以较 GetStringUTFChars 使用要简单。
    GetStringUTFRegion 很有用,因为你不能修改 GetStringUTFChars 返回值,所以需要另外 malloc/strcpy 之后,再操作返回值,耗时费力,不如直接使用 GetStringUTFRegion 来的简洁、高效。
    对于小尺寸字串的操作,首选 Get/SetStringRegion 和 Get/SetStringUTFRegion,因为栈上空间分配,开销要小的多;而且没有内存分配,就不会有 out-of-memory exception。如果你要操作一个字串的子集,本套函数的 starting index 和 length 正合要求。

    JNI 对每种数据类型的数组都有对应的函数。

Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr){
jint buf[10];
jint i, sum = 0;
(*env)->GetIntArrayRegion(env, arr, 0, 10, buf);
for (i = 0; i < 10; i++) {
sum += buf[i];
}
return sum;
}

    JNI 中数组的基类为 jarray,其他如 jintArray 都继承自 jarray。

    上节示例中,使用 GetIntArrayRegion 拷贝数组内容到 buf 中,这里没有做越界异常检测,因为知道数组有 10 个,参数 3 为待拷贝数组的起始位置,参数 4 为拷贝元素的个数。
    JNI 支持 SetIntArrayRegion 允许重新设置数组一个区域的值,其他基本类型(boolean,short, 和 float)也有对应的支持。
    JNI 支持通过 Get/Release<Type>ArrayElemetns 返回 Java 数组的一个拷贝(实现优良的VM,会返回指向 Java 数组的一个直接的指针,并标记该内存区域,不允许被 GC)。如下所示:

Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr){
jint *carr;
jint i, sum = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
for (i=0; i<10; i++) {
sum += carr[i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum;
}

    GetArrayLength 返回数组元素个数。

转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/use-strings-and-array-in-jni.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注