J.Guillaumin
J.Guillaumin

Reputation: 1641

How to pass multidimensional arrays between Java and C++, with SWIG ?

I would like to send a multidimensional array (short[][] in Java, short int[][] in C++) from Java code to C++ code, to process some coputations, then get back the array in Java code !

It works fine for one-dimensional arrays.

example.i :

%module example

%{
#include <iostream>
#include "example.h"
%}

%typemap(jtype) short int values[] "short[]"
%typemap(jstype) short int[] "short[]"
%typemap(javain) short int values[] "$javainput"
%typemap(jni) short int values[] "jshortArray"
%typemap(in) short int values[] {
  jboolean isCopy;
  $1 = JCALL2(GetShortArrayElements, jenv, $input, &isCopy);
}

%typemap(in,numinputs=0,noblock=1) int *data_len {
   int temp_len;
   $1 = &temp_len;
}


%typemap(jstype) const short int *CPPtoJava "short[]"
%typemap(jtype) const short int *CPPtoJava "short[]"
%typemap(jni) const short int *CPPtoJava "jshortArray"

%typemap(javaout) const short int *CPPtoJava {
  return $jnicall;
}


%typemap(out) const short int *CPPtoJava {
  $result = JCALL1(NewShortArray, jenv, temp_len);
  JCALL4(SetShortArrayRegion, jenv, $result, 0, temp_len, $1);
}



%include "example.h"

example.h :

class Test
{
public:

    short int* data;
    size_t l;

    void JavaToCPP(short int values[], size_t len) {
        this->l = len;
        this->data = values;
    }

    const short int *CPPtoJava(int *data_len){
        *data_len = this->l ;
        return this->data;
    }

    void process(){
        for(int i = 0 ; i< this->l ; i++){
            this->data[i] = i;
        }
    }


};

MainRunner.java

public class MainRunner {
    public static void main(String[] argv) {
        System.load("/path/to/../libexample.so");

        short in[] = {0,0,0};
        System.out.println("\nInput : ");
        for (int i = 0; i < in.length; ++i) {
              System.out.print(in[i] + "\t");
        }


        Test t = new Test(); 
        t.JavaToCPP(in,(long)in.length);
        t.process();


        short[] out = t.CPPtoJava();
        System.out.println("\n\nOutput : ");
        for (int i = 0; i < out.length; ++i) {
              System.out.print(out[i] + "\t");
        }
      }
}

Do you know how to handle mutlidimensional arrays in Swig ?

Thanks for your help !

Upvotes: 2

Views: 804

Answers (1)

I created a header file to illustrate the kind of cases where you might want to use this. To support non-square 2D arrays it has w and h as the two size parameters for the output function.

It's slightly simpler than your example case and targeting C, not C++ (but the beauty of the JCALLx macros is they work for either language). (NB: If you're using C++ just use std::array or std::vector as appropriate instead and get all the wrapping for free).

#include <stdlib.h>
#include <string.h>

short **CPPtoJava(size_t *w, size_t *h) {
  *w = 3;
  *h = 4;
  short **data = malloc(sizeof(short*) * *w);
  for (size_t i = 0; i < *w ; ++i) {
    data[i] = malloc(sizeof(short) * *h);
    for (size_t j = 0; j < *h; ++j) {
      data[i][j] = (1+i)*(1+j);
    }
  }
  return data;
}

void JavaToCPP(short **values, size_t w, size_t h) {
  for (size_t i = 0; i < w; ++i) {
    for (size_t j = 0; j < h; ++j) {
      printf("(%zu, %zu): %d\n", i, j, (int)values[i][j]);
    }
  }
}

With that in place we can write some typemaps to marshal the data in and out of Java. They're not much different to the 1D case, except that we setup and build N 1D arrays and 1 array of arrays.

%module test

%{
#include "test.h"
#include <assert.h>
%}

// For the outputs
%typemap(jni) short ** "jobjectArray"
%typemap(jstype) short ** "short[][]"
%typemap(jtype) short ** "short[][]"
%typemap(javaout) short ** {
  return $jnicall;
}
%typemap(in,numinputs=0) size_t *w %{
  size_t w=0;
  $1 = &w;  
%}
%typemap(in,numinputs=0) size_t *h %{
  size_t h=0;
  $1 = &h;
%}
%typemap(out) short ** {
  $result = JCALL3(NewObjectArray, jenv, w, JCALL1(FindClass, jenv, "[S"), NULL);
  for (size_t i = 0; i < w; ++i) {
    jshortArray cur = JCALL1(NewShortArray, jenv, h);
    JCALL4(SetShortArrayRegion, jenv, cur, 0, h, $1[i]);
    JCALL3(SetObjectArrayElement, jenv, $result, i, cur);
  }
  free(result); // Since I called malloc this is needed
}


// For the inputs

%typemap(jni) (short **values, size_t w, size_t h) "jobjectArray"
%typemap(jstype) (short **values, size_t w, size_t h) "short[][]"
%typemap(jtype) (short **values, size_t w, size_t h) "short[][]"
%typemap(javain) (short **values, size_t w, size_t h) "$javainput"
%typemap(in,numinputs=1,noblock=1) (short **values, size_t w, size_t h) {
  $2 = JCALL1(GetArrayLength, jenv, $input);
  $1 = alloca(sizeof(short*) * $2); // Or maybe not on stack if you prefer?
  $3 = 0;
  if ($2 > 0) {
    jshortArray dim1 = JCALL2(GetObjectArrayElement, jenv, $input, 0);
    $3 = JCALL1(GetArrayLength, jenv, dim1);
  }

  for (size_t i = 0; i < $2; ++i) {
    jshortArray cur = JCALL2(GetObjectArrayElement, jenv, $input, i);
    assert((size_t)JCALL1(GetArrayLength, jenv, cur) == $3); // TODO: handle error
    $1[i] = JCALL2(GetShortArrayElements, jenv, cur, NULL);
  }
}
%typemap(freearg) (short **values, size_t w, size_t h) {
  for (size_t i = 0; i < $2; ++i) {
    jshortArray cur = JCALL2(GetObjectArrayElement, jenv, $input, i);
    JCALL3(ReleaseShortArrayElements, jenv, cur, $1[i], JNI_ABORT);
  }
}

%include "test.h"

There's an assert hiding in there to prevent people from creating irregularly shaped (i.e. non-rectangular 2D arrays)

With that in place we can then run:

import java.util.Arrays;

public class run {
  private static void print(short[][] arr) {
    for (int i = 0; i < arr.length; ++i) {
      System.out.println(Arrays.toString(arr[i]));
    }
  }

  public static void main(String[] argv) {
    System.loadLibrary("test");
    short[][] r1 = test.CPPtoJava();
    print(r1);
    test.JavaToCPP(r1);
  }
}

Upvotes: 1

Related Questions