Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert to type SWIGTYPE_p_void in SWIG generated Java bindings?

I am developing some SWIG-generated Java bindings for a C library. The library contains functions that take parameters of type void *. On the C side these would typically be passed as a pointer to an array of type float or int cast to void *. In the generated Java bindings, this results in methods that take parameters of type SWIGTYPE_p_void.

What is the best way to construct an array of floats/ints in the Java bindings so that they can be passed as type SWIGTYPE_p_void to these methods?

At the moment I am defining a helper function in my example.i file:

void *floata_to_voidp(float f[])
{
    return (void *)f;
}

And then on the Java side doing something like this:

float foo[] = new float[2];
SWIGTYPE_p_void av = null;

// do something with foo

av = example.floata_to_voidp(foo);
example.myfunction(av);

This seems rather ugly, especially as I would also need an inta_to_voidp() etc in my SWIG interface file for each type conversion I want to support.

Is there a way to do this without helper functions and involving less extra code on the Java side to convert data types?

UPDATE (17/6/12): to give additional detail to the question: what I'm trying to do is take a set of C functions, with prototype int foo(const float *data, int N, const void *argv, float *result) and map them to methods on the Java side where an array of arbitrary type can be passed in as argv. Note that argv is const void * and not void *.

like image 575
j b Avatar asked Oct 08 '22 13:10

j b


1 Answers

The simplest solution is to use SWIG's <carrays.i> to make a type that wraps an array of float and an array of int and any other types you care about. These can be converted to SWIGTYPE_p_float etc. trivially using the cast() member function. The problem is that this can't automatically be converted to a SWIGTYPE_p_void from within Java. You could in theory call:

new SWIGTYPE_p_void(FloatArray.getCPtr(myfloatarr));

but for various reasons (not least it's cumbersome) that's less than ideal. (It also has issues with memory ownership and garbage collection).

So instead I defined an interface:

public interface VoidPtr {
  public long asVoidPtr();
}

That we can make the wrapped version of your library take as input and the array classes FloatArray, IntArray etc. implement for us.

This ends up with the module file:

%module test

%include <carrays.i>

%typemap(javainterfaces) FloatArray "VoidPtr"
%typemap(javainterfaces) IntArray "VoidPtr"

%typemap(javacode) FloatArray %{
  public long asVoidPtr() {
    return getCPtr(this);    
  }
%}

%typemap(javacode) IntArray %{
  public long asVoidPtr() {
    return getCPtr(this);
  }
%}

%array_class(float, FloatArray);
%array_class(int, IntArray);

%typemap(jstype) void *arr "VoidPtr"
%typemap(javain) void *arr "$javainput.asVoidPtr()"

void foo(void *arr);

Which modifies void *arr to be treated as our VoidPtr type and automatically calls the asVoidPtr() method. You could use typemap copying or macros to make this less repetitive. (Note, there's a possible issue with premature garbage collection that might need to be addressed here depending on how you planned to use this)

This allows us to write code like:

public class run {
  public static void main(String[] argv) {
    FloatArray arr = new FloatArray(100);
    test.foo(arr);    
  }
}

I think this is the easiest, cleanest solution. There are several other ways you could solve this though:

  1. It's also possible to write some code that would take an actual Java array rather than just the SWIG array_class and implement this interface by calling a JNI function to obtain the underlying pointer. You'd have to write a version of this for every primitive type though, just like the above.

    An interface file could then look something like:

    %module test
    
    %{
    void foo(void *arr);
    %}
    
    %typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;"
    
    %rename(foo) fooFloat;
    %rename(foo) fooInt;
    %inline %{
    void fooFloat(JNIEnv *env, jfloatArray arr) {
      jboolean isCopy;
      foo((*env)->GetFloatArrayElements(env, arr, &isCopy));
      // Release after call with desired semantics
    }
    
    void fooInt(JNIEnv *env, jintArray arr) {
      jboolean isCopy;
      foo((*env)->GetIntArrayElements(env, arr, &isCopy));
      // Release after call
    }
    %}
    
    void foo(void *arr);
    

    Which then gives you overloads of foo which take float[] and int[] as well as SWIGTYPE_p_void.

  2. You could use a trick with a union:

    %inline %{
      union Bodge {
        void *v;
        float *f;
        int *i;
      };
    %}
    

    although this is considered bad form, it does generate you a Java interface that can be used to convert from SWIGTYPE_p_int to SWIGTYPE_p_void.

  3. I think it's possible to make FloatArray inherit from SWIGTYPE_p_void, something like the following compiled but untested code:

    %module test
    
    %include <carrays.i>
    
    %typemap(javabase) FloatArray "SWIGTYPE_p_void"
    %typemap(javabody) FloatArray %{
      private long swigCPtr; // Minor bodge to work around private variable in parent
      private boolean swigCMemOwn;
      public $javaclassname(long cPtr, boolean cMemoryOwn) {
        super(cPtr, cMemoryOwn);
        this.swigCPtr = SWIGTYPE_p_void.getCPtr(this);
        swigCMemOwn = cMemoryOwn;
      }
    %}
    
    %array_class(float, FloatArray);
    
    void foo(void *arr);
    

    This duplicates the pointer on the Java side, but nothing changes that (currently) in either the void pointer or array classes so that's not as big a problem as it first seems. (You could also make it protected in the base class with an alternative typemap I think, or use a modified version of carrays.i that gets swigCPtr via the getCPtr function instead)

like image 146
Flexo Avatar answered Oct 20 '22 21:10

Flexo