使用 SWIG 的 C 函数的 JNI 包装器 - 类型映射应该是什么?

2022-01-25 00:00:00 c swig java java-native-interface

I am trying to create the JNI wrapper for the following functions in C:

int err = new_instance(const char* name, instance_t* instance);

name - input, instance - output

int err = get_value(const instance_t instance, int *val);

instance - input, val - output

where instance_t is defined as:

typedef void* instance_t;

I am all lost in the SWIG manual for Java, since it doesn't simply support input parameters as the output type. I had no problems whatsoever with the Python wrapper (shown below).

What is the correct way of using typemap in the case of Java?

// instance_t [argout]
%typemap(in, numinputs=0) instance_t* instance (instance_t temp = 0) {
    $1 = &temp;
}
%typemap(argout) instance_t *instance {
    %append_output(PyLong_FromLongLong((long long)* $1));
}
// instance_t [in]
%typemap(in) instance_t instance {
    $1 = (instance_t) PyLong_AsLongLong($input);
}

解决方案

You can do this using SWIG and Java in several different ways. I've created the following header to illustrate all my examples, based on what you showed in the question:

typedef void* instance_t;

int new_instance(const char* name, instance_t * instance);
int get_value(const instance_t instance, int *val);

Writing some Java in the interface:

We can use the cpointer.i from the SWIG library to give us the functions we need to write a Java overload that calls the default version of new_instance (which we make private since it becomes an implementation detail).

%module test

%{
#include "test.h"
%}

%include <cpointer.i>
// Have SWIG create a helper class for "pointer to pointer" type of handle
%pointer_class(instance_t, inst_ptr);
// Hide default version of new_instance
%javamethodmodifiers new_instance "private";

// Supply  Java version of new_instance now with useful method signature
%pragma(java) modulecode=%{
  public static SWIGTYPE_p_void new_instance(String name) {
    inst_ptr ptr = new inst_ptr();
    final int err = new_instance(name, ptr.cast());
    if (0!=err) {
      // throw or whatever
    }
    return ptr.value();
  }
%}

%include "test.h"

Note that this example probably leaks as is since ptr.value() is non-owning by default.

Writing some C in the interface:

In this next example we write an "overload" (but since I assumed you're writing C and not C++ we had to use %rename to make this work) in C alone, specifically for the SWIG interface. The original version of the function gets ignored completely since it's pretty useless to us.

%module test

%{
#include "test.h"
%}

// Hide the default new_instance
%ignore new_instance;
%include "test.h"
// Pretend our wrapper specific "overload" was called new_instance all along
%rename(new_instance) new_instance_overload;
// Don't leak our new instance
%newobject new_instance;

// Declare, define and wrap a special version of new_instance
%inline %{
    instance_t new_instance_overload(const char* name) {
        instance_t result = NULL;
        const int err = new_instance(name, &result);
        if (err) {
            // See later on/other Q for cross language exception example
        }
        return result;
    }
%}

Using typemaps

We can actually do something very similar to your Python example using Java typemaps, although the process is more convoluted since Java has strong typing and we need to respect that.

This solution is also substantially similar to my older answer on the same underlying issue, with the additional complexity that getting strong typing working in Java (instead of just SWIGTYPE_p_void) is trickier here when the underlying typedef is to void* instead of a forward declaration of a struct.

%module test

%{
#include "test.h"
%}

// Provide 'instance' class for strong typing (instead of void* semantics)
%rename(Instance) instance;
%nodefaultctor;
struct instance {};
typedef instance * instance_t;

// Don't leak (not that we have a destructor yet, but...)
%newobject new_instance;

// Change new_instance to return instance of Instance

%typemap(jstype) int new_instance "$typemap(jstype,instance_t)";
%typemap(jni) int new_instance "$typemap(jni,instance_t)";
%typemap(jtype) int new_instance "$typemap(jtype,instance_t)";

// Hide the instance_t argument and use a temporary instead under the hood
%typemap(in,numinputs=0) instance_t * ($1_basetype tmp) %{
  $1 = &tmp;
%}
// After the call copy the result back
%typemap(argout) instance_t * %{
  *($1_ltype)&$result = *$1;
%}
// Inside Java construct the proxy with the correct long pointer
%typemap(javaout) int new_instance {
  return new $typemap(jstype,int new_instance)($jnicall, $owner);
}

// Some error handling
%javaexception("Exception") new_instance {
  $action
  if (!result) {
    // JNI code to raise exception, untested in this form
    jclass clazz = JCALL1(FindClass, jenv, "Exception");
    JCALL2(ThrowNew, jenv, clazz, "Failure creating thing");
    return $null;
  }
}

%include "test.h"

I would encourage you to look at the generated code around the call to new_instance() to fully understand what these typemaps are doing.

As far as the call to get_value is concerned the instance_t gets handled automatically from the interface above and the int* arg out needs to get handled either similary to the above example, or using a trick with an array containing only one element:

%include <typemaps.i>
%apply int *OUTPUT { int *val };

%include "test.h"

Which you would then call as:

int outarr[] = new int[1];
final int err = test.get_value(instance, outarr);
// outarr[0] then constains the value 

Of course you could then take that trick and use something like the %pragma(java) modulecode of my first example in this answer to supply another overload that behaves more naturally:

%javamethodmodifiers get_value "private";

%pragma(java) modulecode=%{
  public static int get_value(Instance instance) {
    int outarr[] = new int[1];
    final int err = test.get_value(instance, outarr);
    if (0!=err) {
      // throw, blah blah
    }
    return outarr[0];
  }
%}

(Note that this trick with arrays would also have worked for a fourth solution to the instance_t* problem, but because it's not a primitive type there's a lot more work involved for no real gain)

相关文章