Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing 2d array from C++ to Java and back to C++

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

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

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.

Hope you can guide me along, Thanks!

like image 776
JustStarted Avatar asked Mar 26 '15 10:03

JustStarted


1 Answers

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:

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).

Calling the Java function

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

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

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

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[].

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. 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.

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:

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.

1 except by pointer to pass it back into native code

like image 128
Wintermute Avatar answered Sep 22 '22 19:09

Wintermute