SWIG interface to receive an opaque struct reference in Java through function argument
At the most basic level you can make code that works using the cpointer.i part of the SWIG library to allow a direct "pointer to pointer" object to be created in Java.
For example given the header file:
#include <stdlib.h>
typedef struct sp_session sp_session;
typedef struct {} sp_session_config;
typedef int sp_error;
inline sp_error sp_session_create(const sp_session_config *config, sp_session **sess) {
// Just for testing, would most likely be internal to the library somewhere
*sess = malloc(1);
(void)config;
return sess != NULL;
}
// Another thing that takes just a pointer
inline void do_something(sp_session *sess) {}
You can wrap it with:
%module spotify
%{
#include "test.h"
%}
%include "test.h"
%include <cpointer.i>
%pointer_functions(sp_session *, SessionHandle)
Which then allows us to write something like:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
SWIGTYPE_p_p_sp_session session = spotify.new_SessionHandle();
spotify.sp_session_create(new sp_session_config(), session);
spotify.do_something(spotify.SessionHandle_value(session));
}
}
in Java. We use SessionHandle_value()
to derference the double pointer and new_SessionHandle()
to create a double pointer object for us. (There are other functions for working with the double pointer object).
The above works and is very simple to wrap, but it's hardly "intuitive" for a Java programmer and ideally we'd expose the whole library in something that looks more like Java.
A Java programmer would expect that the new session handle object would be returned from the creator function and that an exception would be used to indicate failures. We can make SWIG generate that interface with a few typemaps and some careful use of %exception
, by changing the interface file somewhat:
%module spotify
%{
#include "test.h"
%}
// 1:
%nodefaultctor sp_session;
%nodefaultdtor sp_session;
struct sp_session {};
// 2:
%typemap(in,numinputs=0) sp_session ** (sp_session *tptr) {
$1 = &tptr;
}
// 3:
%typemap(jstype) sp_error sp_session_create "$typemap(jstype,sp_session*)"
%typemap(jtype) sp_error sp_session_create "$typemap(jtype,sp_session*)"
%typemap(jni) sp_error sp_session_create "$typemap(jni,sp_session*)";
%typemap(javaout) sp_error sp_session_create "$typemap(javaout,sp_session*)";
// 4:
%typemap(out) sp_error sp_session_create ""
%typemap(argout) sp_session ** {
*(sp_session **)&$result = *$1;
}
// 5:
%javaexception("SpotifyException") sp_session_create {
$action
if (!result) {
jclass clazz = JCALL1(FindClass, jenv, "SpotifyException");
JCALL2(ThrowNew, jenv, clazz, "Failure creating session");
return $null;
}
}
%include "test.h"
The numbered comments correspond with these points:
- We want the
sp_session
opaque type to map to a "nice" Java type but not allow creation/deletion of the type directly within Java. (If there is asp_session_destroy
function to could arrange for that to get automatically called when the Java object is destroyed if that's desirable using the javadestruct typemap). The fake, empty definition combined with%nodefaultctor
and%nodefaultdtor
arranges for this. - For the input parameter which we're making into a return instead we need to hide it from the Java interface (using
numinputs=0
) and then supply something to take its place in the generated C part of the interface. - To return the
sp_session
instead of the error code we need to adjust the typemaps for the return from the function - the simplest way to do that is to substitute them for the typemaps that would have been used if the function was declared as returning asp_session
using$typemap
. - As far as the output is concerned we don't want to do anything where it would usually be marshaled, but we do want to return the pointer we used as a placeholder for the extra input parameter in 2.
-
Finally we want to enclose the whole call to
sp_session_create
in some code that will check the real return value and map that to a Java exception should it indicate failure. I wrote the following exception class by hand for that as well:public class SpotifyException extends Exception { public SpotifyException(String reason) { super(reason); } }
Having done all this work we are now in a position to use that in Java code as follows:
public class run {
public static void main(String[] argv) throws SpotifyException {
System.loadLibrary("test");
sp_session handle = spotify.sp_session_create(new sp_session_config());
spotify.do_something(handle);
}
}
Which is vastly simpler and more intuitive than the original but simpler to write interface. My inclination would be to use the advanced renaming feature to make the type names "look more Java" also.