Java/JNI技术疑问:直接返回jstring作为jobject给Java是否可行?
JNI绑定SANE时nativeGetCurrentValue调用崩溃问题
我通过JNI实现了SANE与Java的绑定,但调用nativeGetCurrentValue函数时出现崩溃。
Java端代码
native Object nativeGetCurrentValue(long hnd, int num, int type);
Native端代码
JNIEXPORT jobject JNICALL Java_package_Sane_nativeGetCurrentValue(JNIEnv * env, jobject o, jlong hnd, jint num, jint type) { void *value; if (sane_control_option((SANE_Handle *) hnd, num, SANE_ACTION_GET_VALUE, value, NULL) == SANE_STATUS_GOOD) { switch (type) { case SANE_TYPE_BOOL: { // fprintf(stderr, "SANE_TYPE_BOOL...\n"); jboolean * v = (jboolean *)value; jclass cls = env->FindClass("java/lang/Boolean"); jmethodID midBool = env->GetMethodID(cls, "<init>", "(Z)V"); return env->NewObject(cls, midBool, *v); } case SANE_TYPE_FIXED: { // fprintf(stderr, "SANE_TYPE_FIXED...\n"); SANE_Word * v = (SANE_Word *)value; double d = SANE_UNFIX(*v); jclass cls = env->FindClass("java/lang/Double"); jmethodID midDoub = env->GetMethodID(cls, "<init>", "(D)V"); return env->NewObject(cls, midDoub, (jdouble)d); } case SANE_TYPE_INT: { // fprintf(stderr, "SANE_TYPE_INT...\n"); int * v = (int *)value; jclass cls = env->FindClass("java/lang/Integer"); jmethodID midInit = env->GetMethodID(cls, "<init>", "(I)V"); return env->NewObject(cls, midInit, *v); } case SANE_TYPE_STRING: // fprintf(stderr, "SANE_TYPE_STRING...\n"); jstring str = env->NewStringUTF((char *)value); return str; } } return NULL; }
我怀疑自己的实现存在问题,想咨询:使用env->NewStringUTF创建的jstring直接返回给Java是否可行?
问题解答
关于jstring直接返回的可行性
完全可以直接返回env->NewStringUTF创建的jstring给Java,这是符合JNI规范的操作。JNI创建的字符串对象会被Java虚拟机的垃圾回收机制管理,不需要额外手动释放,这部分你的写法没问题。
崩溃的真正原因
你的代码里有个致命问题:void *value;是未初始化的野指针,直接传给sane_control_option的第四个参数会导致内存访问错误,这才是崩溃的根源。
根据SANE文档,sane_control_option的第四个参数是输出参数,需要你预先分配好对应类型的内存空间,或者传入指向有效内存的指针,让函数把值写入进去。你现在传的是未初始化的value,函数会尝试往随机内存地址写数据,必然触发崩溃。
修复方案
针对不同的SANE类型,预先分配对应大小的内存:
JNIEXPORT jobject JNICALL Java_package_Sane_nativeGetCurrentValue(JNIEnv * env, jobject o, jlong hnd, jint num, jint type) { // 根据类型分配对应内存 switch (type) { case SANE_TYPE_BOOL: { SANE_Bool bool_val; if (sane_control_option((SANE_Handle)hnd, num, SANE_ACTION_GET_VALUE, &bool_val, NULL) == SANE_STATUS_GOOD) { jclass cls = env->FindClass("java/lang/Boolean"); jmethodID midBool = env->GetMethodID(cls, "<init>", "(Z)V"); return env->NewObject(cls, midBool, (jboolean)bool_val); } break; } case SANE_TYPE_FIXED: { SANE_Word fixed_val; if (sane_control_option((SANE_Handle)hnd, num, SANE_ACTION_GET_VALUE, &fixed_val, NULL) == SANE_STATUS_GOOD) { double d = SANE_UNFIX(fixed_val); jclass cls = env->FindClass("java/lang/Double"); jmethodID midDoub = env->GetMethodID(cls, "<init>", "(D)V"); return env->NewObject(cls, midDoub, (jdouble)d); } break; } case SANE_TYPE_INT: { SANE_Word int_val; // SANE的int类型实际是SANE_Word if (sane_control_option((SANE_Handle)hnd, num, SANE_ACTION_GET_VALUE, &int_val, NULL) == SANE_STATUS_GOOD) { jclass cls = env->FindClass("java/lang/Integer"); jmethodID midInit = env->GetMethodID(cls, "<init>", "(I)V"); return env->NewObject(cls, midInit, (jint)int_val); } break; } case SANE_TYPE_STRING: { // 字符串建议先获取所需长度再动态分配,这里先用固定缓冲区示例 char str_buf[1024]; if (sane_control_option((SANE_Handle)hnd, num, SANE_ACTION_GET_VALUE, str_buf, NULL) == SANE_STATUS_GOOD) { return env->NewStringUTF(str_buf); } break; } } return NULL; }
额外注意事项
SANE_Handle本身就是指针类型,不需要强转成(SANE_Handle *),直接用(SANE_Handle)hnd即可。- 字符串类型最好先通过
sane_control_option获取缓冲区大小,再动态分配内存,避免固定缓冲区不够用。 - 可以缓存
FindClass和GetMethodID的结果,减少重复调用的性能开销。
内容的提问来源于stack exchange,提问作者Thiago Sayão
相关产品推荐
相关产品推荐

