7

我正在尝试使用 SWIG 来使用适用于 Android 的 Spotify API (libspotify): https ://developer.spotify.com/technologies/libspotify/

我在定义 SWIG 接口文件以便能够成功调用以下本机 C 函数时遇到问题:

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

在 C 中会这样调用:

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

但在 Java 中,我需要这样称呼它:

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

sp_session 是一个不透明的结构,仅在 libspotify 的 API.h 文件中定义为:

typedef struct sp_session sp_session;

我期待 libspotify 库创建它并给我一个参考。我唯一需要该引用的就是传递给 API 中的其他函数。

我相信答案在 SWIG 界面和类型映射中,但我在尝试应用我在文档中找到的示例时没有成功。

4

2 回答 2

6

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.

于 2012-10-07T09:33:54.400 回答
0

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.

于 2012-10-07T09:35:24.803 回答