I am trying to use SWIG in order to use the Spotify API (libspotify) for Android: https://developer.spotify.com/technologies/libspotify/
I am having trouble definin
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:
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 a sp_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.numinputs=0
) and then supply something to take its place in the generated C part of the interface.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 a sp_session
using $typemap
.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.
You should inform swig about your typedef
declaration. In order to do that you should edit your interface file with :
typedef struct sp_session sp_session;
But be careful to inform SWIG about your typedef before it sees any sp_session (I mean before including API.h). I had the same problem with typedef recognition. Maybe this link will help.