如何使用JNI访问对象内的数组?

34

例如这个JNI教程相当详细地介绍了如何访问对象内的原始字段,以及如何访问作为显式函数参数(即jarray子类)提供的数组。但是如何访问作为jobject字段的Java(原始)数组呢?例如,我想操作以下Java对象的字节数组:

class JavaClass {
  ...
  int i;
  byte[] a;
}

主程序可能是这样的:

class Test {

  public static void main(String[] args) {
    JavaClass jc = new JavaClass();
    jc.a = new byte[100];
    ...
    process(jc);
  }

  public static native void process(JavaClass jc);
}

相应的C++端代码如下:

JNIEXPORT void JNICALL Java_Test_process(JNIEnv * env, jclass c, jobject jc) {

  jclass jcClass = env->GetObjectClass(jc);
  jfieldID iId = env->GetFieldID(jcClass, "i", "I");

  // This way we can get and set the "i" field. Let's double it:
  jint i = env->GetIntField(jc, iId);
  env->SetIntField(jc, iId, i * 2);

  // The jfieldID of the "a" field (byte array) can be got like this:
  jfieldID aId = env->GetFieldID(jcClass, "a", "[B");

  // But how do we operate on the array???
}

我考虑使用GetByteArrayElements,但它需要一个ArrayType作为其参数。 显然我错过了什么。 有没有一种方法可以做到这一点?

2个回答

45

我希望这能对您有所帮助(也请查看JNI结构体参考):

// Get the class
jclass mvclass = env->GetObjectClass( *cls );
// Get method ID for method getSomeDoubleArray that returns a double array
jmethodID mid = env->GetMethodID( mvclass, "getSomeDoubleArray", "()[D");
// Call the method, returns JObject (because Array is instance of Object)
jobject mvdata = env->CallObjectMethod( *base, mid);
// Cast it to a jdoublearray
jdoubleArray * arr = reinterpret_cast<jdoubleArray*>(&mvdata)
// Get the elements (you probably have to fetch the length of the array as well
double * data = env->GetDoubleArrayElements(*arr, NULL);
// Don't forget to release it 
env->ReleaseDoubleArrayElements(*arr, data, 0); 

好的,这里我使用方法而不是字段(我考虑调用Java getter会更清晰),但你可能也可以将其重写为字段。不要忘记释放,并且如评论中所说,你可能仍然需要获取长度。

编辑:将您的示例重写为字段获取。基本上将CallObjectMethod替换为GetObjectField。

JNIEXPORT void JNICALL Java_Test_process(JNIEnv * env, jclass c, jobject jc) {

  jclass jcClass = env->GetObjectClass(jc);
  jfieldID iId = env->GetFieldID(jcClass, "i", "I");

  // This way we can get and set the "i" field. Let's double it:
  jint i = env->GetIntField(jc, iId);
  env->SetIntField(jc, iId, i * 2);

  // The jfieldID of the "a" field (byte array) can be got like this:
  jfieldID aId = env->GetFieldID(jcClass, "a", "[B");

  // Get the object field, returns JObject (because Array is instance of Object)
  jobject mvdata = env->GetObjectField (jc, aID);

  // Cast it to a jdoublearray
  jdoubleArray * arr = reinterpret_cast<jdoubleArray*>(&mvdata)

  // Get the elements (you probably have to fetch the length of the array as well  
  double * data = env->GetDoubleArrayElements(*arr, NULL);

  // Don't forget to release it 
  env->ReleaseDoubleArrayElements(*arr, data, 0);
}

谢谢,使用getter方法是聪明的选择(也许甚至更加简洁)。除非有人指出如何以GetXXXField的方式直接获取数组字段,否则我将不得不这样做。 - Joonas Pulakka
1
好的,我为该字段添加了一个示例(基本上只需使用GetObjectField而不是CallObjectMethod)。虽然我当然不能保证它可以直接运行,但我希望你可以理解其一般思路 :) - Daff
2
没错!我本来期望能找到更简单的方法来做这件事,所以我不太愿意回到定义上(“数组是一个对象” :-) 这就是编程心理学... 再次感谢! - Joonas Pulakka
我在其他答案中也看到了同样的问题。为什么是 double * data?而不是 jdouble * data - Maksim Dmitriev
为什么要使用reinterpret_cast而不是static_cast? - Andros
虽然这是一个老问题,但是@Andros指出,static_cast禁止将jobject转换为jdoubleArray,尽管根据此链接,jdoublearray是从jobject继承而来的。 - Zoso

7
在gcc 6.3中,我会收到一个警告,提示“解引用类型转换的指针将违反严格别名规则”,就像这一行代码一样:
jdoubleArray arr = *reinterpret_cast<jdoubleArray*>(&mvdata);

但是,由于jdoubleArray本身就是指向类_jdoubleArray的指针,所以在强制转换之前不需要获取地址,而且这个static_cast可以正常工作,没有警告:

jdoubleArray arr = static_cast<jdoubleArray>(mvdata);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接