将2d数组从c++传递到Java,再返回到c++。

时间:2022-10-04 13:12:21

How can I pass a 2d array from C++ to Java and return back to C++ using JNI?

如何将2d数组从c++传递到Java,然后使用JNI返回到c++ ?

Sample2.java

Sample2.java

public static int[][] intArrayMethod(int[][] n){
    for (int i = 0; i < 10; i++){
        for (int j = 0; j < 10; j++){
            n[i][j] = 2;
        }
    }
    return n;
}

CppSample.cpp

CppSample.cpp

int main(){
    JavaVMOption options[1];
    JNIEnv *env;
    JavaVM *jvm;
    JavaVMInitArgs vm_args;
    long status;
    jclass cls;
    jmethodID mid;

    options[0].optionString = "-Djava.class.path=.";
    memset(&vm_args, 0, sizeof(vm_args));
    vm_args.version = JNI_VERSION_1_2;
    vm_args.nOptions = 1;
    vm_args.options = options;
    status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

    jint cells[10][10]; // my 2d array
    for (int i = 0; i < 10; i++){
        for (int j = 0; j < 10; j++){
            cells[i][j] = 1;
        }
    }

    if (status != JNI_ERR){
        cls = env->FindClass("Sample2");
        if (cls != 0){
            mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");
            if (mid != 0){
                cells = env->CallStaticIntMethod(cls, mid, cells);
                for (int i = 0; i < 10; i++){
                    for (int j = 0; j < 10; j++){
                        printf("%d", cells[i][j]);
                    }
                }
            }
        }
        jvm->DestroyJavaVM();
        return 0;
    }
    else{
        return 1;
    }
}

I'm not sure what is the correct way to pass a 2d array between c++ and java.

我不确定在c++和java之间传递2d数组的正确方式是什么。

Hope you can guide me along, Thanks!

希望您能给我带路,谢谢!

1 个解决方案

#1


5  

This is not so much difficult as tedious. The general problem is that arrays in Java are completely unlike arrays in C++ and (sort of) more comparable to non-resizeable vectors. It is not possible to pass a C++ array to Java directly1 because Java has no idea what to do with it. So we'll need conversion functions such as:

这与其说是困难,不如说是乏味。一般的问题是,Java中的数组与c++中的数组完全不同,并且(某种程度上)更类似于不可大小向量。将c++数组传递给Java directly1是不可能的,因为Java不知道如何使用它。所以我们需要转换函数,例如:

Converting the 2D array from C++ to Java

// The template bit here is just to pick the array dimensions from the array
// itself; you could also pass in a pointer and the dimensions. 
template<std::size_t OuterDim, std::size_t InnerDim>
jobjectArray to_java(JNIEnv *env, int (&arr)[OuterDim][InnerDim]) {
  // We allocate the int array first
  jintArray    inner = env->NewIntArray   (InnerDim);
  // to have an easy way to get its class when building the outer array
  jobjectArray outer = env->NewObjectArray(OuterDim, env->GetObjectClass(inner), 0);

  // Buffer to bring the integers in a format that JNI understands (jint
  // instead of int). This step is unnecessary if jint is just a typedef for
  // int, but on OP's platform this appears to not be the case.
  std::vector<jint> buffer;

  for(std::size_t i = 0; i < OuterDim; ++i) {
    // Put the data into the buffer, converting them to jint in the process
    buffer.assign(arr[i], arr[i] + InnerDim);

    // then fill that array with data from the input
    env->SetIntArrayRegion(inner, 0, InnerDim, &buffer[0]);
    // and put it into the outer array
    env->SetObjectArrayElement(outer, i, inner);

    if(i + 1 != OuterDim) {
      // if required, allocate a new inner array for the next iteration.
      inner = env->NewIntArray(InnerDim);
    }
  }

  return outer;
}

From Java to C++

// Note that this function does not return an array but a vector of vectors
// because 2-dimensional Java arrays need not be rectangular and have
// dynamic size. It is not generally practical to map them to C++ arrays.
std::vector<std::vector<int> > from_java(JNIEnv *env, jobjectArray arr) {
  // always on the lookout for null pointers. Everything we get from Java
  // can be null.
  jsize OuterDim = arr ? env->GetArrayLength(arr) : 0;
  std::vector<std::vector<int> > result(OuterDim);

  for(jsize i = 0; i < OuterDim; ++i) {
    jintArray inner = static_cast<jintArray>(env->GetObjectArrayElement(arr, i));

    // again: null pointer check
    if(inner) {
      // Get the inner array length here. It needn't be the same for all
      // inner arrays.
      jsize InnerDim = env->GetArrayLength(inner);
      result[i].resize(InnerDim);

      jint *data = env->GetIntArrayElements(inner, 0);
      std::copy(data, data + InnerDim, result[i].begin());
      env->ReleaseIntArrayElements(inner, data, 0);
    }
  }

  return result;
}

As you can see, they're pretty straightforward; one really only has to know what JNI functions to call (or, indeed, where to find the documentation).

你们可以看到,它们非常简单;我们只需要知道调用什么JNI函数(或者,实际上,在哪里找到文档)。

Calling the Java function

As for calling the Java function, you nearly have it right. Two things need changing:

至于调用Java函数,几乎是正确的。两件事需要改变:

mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");

would work if intArrayMethod were int intArrayMethod(int). As it is, you need

如果intArrayMethod是int intArrayMethod(int),则可以工作。事实上,你需要。

mid = env->GetStaticMethodID(cls, "intArrayMethod", "([[I)[[I");

In these signature strings, I stands for int, [I for int[] and [[I for int[][]. In general, [type stands for type[].

在这些签名字符串中,我代表int, [I for int[]和[[I for int[]]。一般来说,[type]代表type[]。

And here:

在这里:

cells = env->CallStaticIntMethod(cls, mid, cells);

Two things:

两件事:

  1. As mentioned in the very beginning, cells cannot be passed as-is because Java doesn't understand what it is; it needs to be converted to a Java array first (with, for example, the conversion function above), and
  2. 正如一开始提到的,单元格不能按原样传递,因为Java不理解它是什么;它需要首先转换为Java数组(例如,使用上面的转换函数)和
  3. intArrayMethod is not an IntMethod; it does not return an int but a complex data type. The correct call has to use CallStaticObjectMethod and cast the jobject it returns to the actually useful JNI type.
  4. intArrayMethod不是IntMethod;它不返回一个int类型,而是一个复杂的数据类型。正确的调用必须使用CallStaticObjectMethod并将其返回到真正有用的JNI类型的jobject。

A correct way to do it is:

正确的做法是:

jobjectArray java_cells = static_cast<jobjectArray>(env->CallStaticObjectMethod(cls, mid, to_java(env, cells)));

This gives you a jobjectArray that you can convert to a C++ vector of vectors with the other conversion function:

这给了你一个jobjectArray你可以用另一个变换函数把它转换成一个c++向量:

std::vector<std::vector<int> > cpp_cells = from_java(env, java_cells);

This can then be used like any vector.

这个可以像任何向量一样使用。

There are some style issues I could mention (such as relying on the array always being 10x10 in the Java code or the use or C functions where there are better (read: typesafe) C++ alternatives -- I'm looking at you, printf), but they don't appear to trigger problems in this particular program.

有一些样式我可以提问题(如依靠数组总是被10 x10在Java代码或使用或C函数有更好的(阅读:类型安全)c++的替代品,我看着你,printf),但是他们并不会引发的问题在这个特定的项目。

1 except by pointer to pass it back into native code

除了通过指针将它传回本地代码

#1


5  

This is not so much difficult as tedious. The general problem is that arrays in Java are completely unlike arrays in C++ and (sort of) more comparable to non-resizeable vectors. It is not possible to pass a C++ array to Java directly1 because Java has no idea what to do with it. So we'll need conversion functions such as:

这与其说是困难,不如说是乏味。一般的问题是,Java中的数组与c++中的数组完全不同,并且(某种程度上)更类似于不可大小向量。将c++数组传递给Java directly1是不可能的,因为Java不知道如何使用它。所以我们需要转换函数,例如:

Converting the 2D array from C++ to Java

// The template bit here is just to pick the array dimensions from the array
// itself; you could also pass in a pointer and the dimensions. 
template<std::size_t OuterDim, std::size_t InnerDim>
jobjectArray to_java(JNIEnv *env, int (&arr)[OuterDim][InnerDim]) {
  // We allocate the int array first
  jintArray    inner = env->NewIntArray   (InnerDim);
  // to have an easy way to get its class when building the outer array
  jobjectArray outer = env->NewObjectArray(OuterDim, env->GetObjectClass(inner), 0);

  // Buffer to bring the integers in a format that JNI understands (jint
  // instead of int). This step is unnecessary if jint is just a typedef for
  // int, but on OP's platform this appears to not be the case.
  std::vector<jint> buffer;

  for(std::size_t i = 0; i < OuterDim; ++i) {
    // Put the data into the buffer, converting them to jint in the process
    buffer.assign(arr[i], arr[i] + InnerDim);

    // then fill that array with data from the input
    env->SetIntArrayRegion(inner, 0, InnerDim, &buffer[0]);
    // and put it into the outer array
    env->SetObjectArrayElement(outer, i, inner);

    if(i + 1 != OuterDim) {
      // if required, allocate a new inner array for the next iteration.
      inner = env->NewIntArray(InnerDim);
    }
  }

  return outer;
}

From Java to C++

// Note that this function does not return an array but a vector of vectors
// because 2-dimensional Java arrays need not be rectangular and have
// dynamic size. It is not generally practical to map them to C++ arrays.
std::vector<std::vector<int> > from_java(JNIEnv *env, jobjectArray arr) {
  // always on the lookout for null pointers. Everything we get from Java
  // can be null.
  jsize OuterDim = arr ? env->GetArrayLength(arr) : 0;
  std::vector<std::vector<int> > result(OuterDim);

  for(jsize i = 0; i < OuterDim; ++i) {
    jintArray inner = static_cast<jintArray>(env->GetObjectArrayElement(arr, i));

    // again: null pointer check
    if(inner) {
      // Get the inner array length here. It needn't be the same for all
      // inner arrays.
      jsize InnerDim = env->GetArrayLength(inner);
      result[i].resize(InnerDim);

      jint *data = env->GetIntArrayElements(inner, 0);
      std::copy(data, data + InnerDim, result[i].begin());
      env->ReleaseIntArrayElements(inner, data, 0);
    }
  }

  return result;
}

As you can see, they're pretty straightforward; one really only has to know what JNI functions to call (or, indeed, where to find the documentation).

你们可以看到,它们非常简单;我们只需要知道调用什么JNI函数(或者,实际上,在哪里找到文档)。

Calling the Java function

As for calling the Java function, you nearly have it right. Two things need changing:

至于调用Java函数,几乎是正确的。两件事需要改变:

mid = env->GetStaticMethodID(cls, "intArrayMethod", "(I)I");

would work if intArrayMethod were int intArrayMethod(int). As it is, you need

如果intArrayMethod是int intArrayMethod(int),则可以工作。事实上,你需要。

mid = env->GetStaticMethodID(cls, "intArrayMethod", "([[I)[[I");

In these signature strings, I stands for int, [I for int[] and [[I for int[][]. In general, [type stands for type[].

在这些签名字符串中,我代表int, [I for int[]和[[I for int[]]。一般来说,[type]代表type[]。

And here:

在这里:

cells = env->CallStaticIntMethod(cls, mid, cells);

Two things:

两件事:

  1. As mentioned in the very beginning, cells cannot be passed as-is because Java doesn't understand what it is; it needs to be converted to a Java array first (with, for example, the conversion function above), and
  2. 正如一开始提到的,单元格不能按原样传递,因为Java不理解它是什么;它需要首先转换为Java数组(例如,使用上面的转换函数)和
  3. intArrayMethod is not an IntMethod; it does not return an int but a complex data type. The correct call has to use CallStaticObjectMethod and cast the jobject it returns to the actually useful JNI type.
  4. intArrayMethod不是IntMethod;它不返回一个int类型,而是一个复杂的数据类型。正确的调用必须使用CallStaticObjectMethod并将其返回到真正有用的JNI类型的jobject。

A correct way to do it is:

正确的做法是:

jobjectArray java_cells = static_cast<jobjectArray>(env->CallStaticObjectMethod(cls, mid, to_java(env, cells)));

This gives you a jobjectArray that you can convert to a C++ vector of vectors with the other conversion function:

这给了你一个jobjectArray你可以用另一个变换函数把它转换成一个c++向量:

std::vector<std::vector<int> > cpp_cells = from_java(env, java_cells);

This can then be used like any vector.

这个可以像任何向量一样使用。

There are some style issues I could mention (such as relying on the array always being 10x10 in the Java code or the use or C functions where there are better (read: typesafe) C++ alternatives -- I'm looking at you, printf), but they don't appear to trigger problems in this particular program.

有一些样式我可以提问题(如依靠数组总是被10 x10在Java代码或使用或C函数有更好的(阅读:类型安全)c++的替代品,我看着你,printf),但是他们并不会引发的问题在这个特定的项目。

1 except by pointer to pass it back into native code

除了通过指针将它传回本地代码