Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

returning an array from C to Java with SWIG

Tags:

java

swig

I have a C function like this:

void get_data(const obj_t *obj, short const **data, int *data_len);

I wrote it like this specifically for Swig, since

const short *get_data(const obj_t *obj, int *data_len);

causes trouble, as SWIG's typemaps aren't smart enough to associate the data_len with the return value.

In Java I want to be able to call this function like this:

short data[]= mylib.get_data(obj);

But I can't figure out how to get the array output parameter to become a return value. With Ruby and Python this works fine, as SWIG for those languages supports returning output params as return values (since the languages can have multiple return values).

How can I get this to work with Java?

like image 956
paleozogt Avatar asked Sep 16 '11 17:09

paleozogt


1 Answers

I've put together the following test header file to demonstrate the problem:

typedef struct { } obj_t;

const short *get_data(const obj_t *obj, int *data_len) {
  (void)obj;
  static short arr[] = {1,2,3,4,5};
  *data_len = sizeof(arr)/sizeof(*arr);
  return arr;
}

I'll talk through the module file I wrote, it starts pretty standard:

%module test

%{
#include "test.h"
%}

Then we set up a typemap for the data_len argument. It doesn't need to be visible on the Java side since the length will be known by the array, but we do need to arrange some storage for the pointer to point to and we make sure it lasts long enough that we can read it later when returning the array to Java too.

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

Then we want SWIG to use short[] on the Java side for the return type:

%typemap(jstype) const short *get_data "short[]"
%typemap(jtype) const short *get_data "short[]"

and jshortArray in the JNI side - there's no need to construct a proxy type, so we just pass the returned value straight through:

%typemap(jni) const short *get_data "jshortArray"
%typemap(javaout) const short *get_data {
  return $jnicall;
}

Finally we create a typemap that's going to create a new array, with size based on the length returned from the function and copy the returned result into the Java array for us. If needed we should free() the real result array here, but in my example it was statically allocated so didn't need to be freed.

%typemap(out) const short *get_data {
  $result = JCALL1(NewShortArray, jenv, temp_len);
  JCALL4(SetShortArrayRegion, jenv, $result, 0, temp_len, $1);
  // If the result was malloc()'d free it here
}

Finally we include the header file for SWIG to wrap, using the typemaps we just wrote:

%include "test.h"

I tested this with:

public class run {
  public static void main(String argv[]) {
    System.loadLibrary("test");
    obj_t obj = new obj_t();
    short[] result = test.get_data(obj);
    for (int i = 0; i < result.length; ++i) {
      System.out.println(result[i]);
    }
  }
}

Which produced:

1
2
3
4
5

For reference you could have wrapped:

void get_data(const obj_t *obj, short const **data, int *data_len);

also though if your function had a way to query the size without setting the array you could wrap this slightly smarter by allocating an array of the correct size on the Java side. To do this you'd want to to write an intermediate function in Java that queried the size, set up the call and then returned the resulting array. This would allow you to use GetShortArrayElements/ReleaseShortArrayElements for a potentially 0 copy call.

This would work because arrays in Java are basically passed by reference, e.g.:

public class ret {
  public static void foo(int arr[]) {
    arr[0] = -100;
  }

  public static void main(String argv[]) {
    int arr[] = new int[10];
    System.out.println(arr[0]);
    foo(arr);
    System.out.println(arr[0]);
  }
}
like image 181
Flexo Avatar answered Sep 30 '22 14:09

Flexo