I am writing a JNI wrapper for a C++ library using SWIG. One of the methods in a library returns an array of structs in an allocated memory:
typedef struct {
int id;
double x;
double y;
} DataStruct;
int get_all_data ( long ref, DataStruct **ppdata, size_t *psize ) {
// ... prepare the data by the ref
*ppdata = (DataStruct*) malloc(sizeof(DataStruct) * size);
*psize = size;
return 0;
}
Method signature in java should look something like this:
native DataStruct[] get_all_data(long ref);
Thus I am expecting SWIG to generate java DataStruct in java and wrapper that would call the library method, then create an jarray of DataStruct in JVM and fill it with DataStruct objects initialized from DataStruct structs returned by the library and finally free the allocated memory pointed by ppdata.
I've been trying to wrap my head around SWIG typemaps for some time and the only solution I see for now is to use %native directive and create JNI implementaion fully manually. Can I get at least some help from SWIG in this case?
The main thing you're looking for here are two features of typemaps. Firstly you want multi argument typemaps to be able to work with arguments that are logically grouped. Secondly you want argout typemaps to have a chance at converting arguments into outputs.
There are a few different ways to write such interfaces inside SWIG, with the main trade-offs covering quantity of C (JNI) vs actual Java that needs to be written. I've put together one complete example:
%module test
%{
#include <assert.h>
%}
%typemap(in,numinputs=0) (DataStruct **ppdata, size_t *psize) (size_t tempsize, DataStruct *tempdata) %{
$2 = &tempsize;
$1 = &tempdata;
%}
%typemap(jtype) int get_all_data "long[]";
%typemap(jstype) int get_all_data "DataStruct[]";
%typemap(javaout) int get_all_data {
final long[] arr = $jnicall;
DataStruct[] ret = new DataStruct[arr.length];
for (int i = 0; i < arr.length; ++i) {
ret[i] = new DataStruct(arr[i], false);
}
return ret;
}
%typemap(jni) int get_all_data "jlongArray";
%typemap(out) int get_all_data %{
// Handle errors in the int return value ($1)
assert(!$1);
%}
%typemap(argout) (DataStruct **ppdata, size_t *psize) {
$result = JCALL1(NewLongArray, jenv, *$2);
jlong temparr[*$2];
for (size_t i = 0; i < *$2; ++i) {
*(DataStruct**)&temparr[i] = &((*$1)[i]);
}
JCALL4(SetLongArrayRegion, jenv, $result, 0, *$2, temparr);
}
%inline %{
typedef struct {
int id;
double x;
double y;
} DataStruct;
int get_all_data ( long ref, DataStruct **ppdata, size_t *psize ) {
static const size_t size = 4;
*ppdata = (DataStruct*) malloc(sizeof(DataStruct) * size);
*psize = size;
for (size_t i = 0; i < size; ++i) {
DataStruct val = {i,i,i};
(*ppdata)[i] = val;
}
return 0;
}
%}
This does a few things:
get_all_data
to be an array. Internally it's an array of longs that represent pointers to DataStruct
instances but the visible part of it is an array of DataStruct objects that get constructed.sizeof
information needed to do so correctly from just the raw output pointer is somewhat tricky so this is a good compromise. (It also means the same code could work if you switched to e.g. linked lists in the future with only small changes).With that in place I can run my test:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
DataStruct[] result = test.get_all_data(0);
for (int i = 0; i < result.length; ++i) {
System.out.println(result[i].getId());
}
}
}
This runs as:
swig2.0 -Wall -java -c++ test.i
gcc -Wall -Wextra -shared -o libtest.so -I/usr/lib/jvm/default-java/include -I/usr/lib/jvm/default-java/include/linux test_wrap.cxx
javac run.java
LD_LIBRARY_PATH=. java run
0
1
2
3
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With