Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
485 views
in Technique[技术] by (71.8m points)

SWIG interface to receive an opaque struct reference in Java through function argument

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 defining the SWIG interface file to be able to successfully call the following native C function:

sp_error sp_session_create(const sp_session_config * config, sp_session ** sess);

Which in C would be called like this:

//config struct defined previously
sp_session *sess;
sp_session_create(&config, &sess);

But in Java I would need to call it like this:

//config object defined previously
sp_session javaSess = new sp_session();
sp_session_create(config, javaSess);

sp_session is an opaque struct and is only defined in libspotify's API.h file as:

typedef struct sp_session sp_session;

I'm expecting the libspotify library to create it and give me a reference to it. The only thing I need that reference for then is to pass to other functions in the API.

I believe the answer lies within the SWIG interface and typemaps, but I have been unsuccessful in trying to apply the examples I found in the documentation.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

  1. 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 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.
  2. 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.
  3. 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 a sp_session using $typemap.
  4. 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.
  5. 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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...