【问题标题】:SWIG interface to receive an opaque struct reference in Java through function argumentSWIG 接口通过函数参数在 Java 中接收不透明的结构引用
【发布时间】:2012-09-26 04:49:56
【问题描述】:

我正在尝试使用 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 界面和类型映射,但我尝试在文档中应用 examples I found 没有成功。

【问题讨论】:

    标签: java swig spotify


    【解决方案1】:

    在最基本的层面上,您可以使用 SWIG 库的 cpointer.i 部分编写代码,以允许在 Java 中创建直接的“指向指针”对象。

    例如给定头文件:

    #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) {}
    

    你可以用:

    %module spotify
    
    %{
    #include "test.h"
    %}
    
    %include "test.h"
    
    %include <cpointer.i>
    
    %pointer_functions(sp_session *, SessionHandle)
    

    这让我们可以编写如下内容:

    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));
      }
    }
    

    在 Java 中。我们使用SessionHandle_value() 来引用双指针,使用new_SessionHandle() 为我们创建一个双指针对象。 (还有其他函数用于处理双指针对象)。


    上面的代码很有效,而且封装起来非常简单,但对于 Java 程序员来说,这很难“直观”,理想情况下,我们会用看起来更像 Java 的东西来公开整个库。

    Java 程序员会期望从创建函数返回新的会话句柄对象,并且会使用异常来指示失败。我们可以让 SWIG 使用一些类型映射和对%exception 的一些谨慎使用来生成该接口,方法是稍微更改接口文件:

    %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"
    

    编号的 cmets 对应于这些点:

    1. 我们希望sp_session opaque 类型映射到“不错的”Java 类型,但不允许直接在 Java 中创建/删除该类型。 (如果有一个 sp_session_destroy 函数可以安排它在 Java 对象被销毁时自动调用,如果需要使用 javadestruct 类型映射)。 fake, empty definition combined with %nodefaultctor and %nodefaultdtor 对此进行了安排。
    2. 对于我们要返回的输入参数,我们需要从 Java 接口中隐藏它(使用 numinputs=0),然后提供一些东西来代替它在接口生成的 C 部分中的位置。李>
    3. 要返回 sp_session 而不是错误代码,我们需要调整函数返回的类型映射 - 最简单的方法是将它们替换为声明函数时将使用的类型映射就像使用 $typemap 返回一个 sp_session
    4. 就输出而言,我们不想在通常会被封送的地方做任何事情,但我们确实希望返回我们用作占位符的指针,用于 2 中的额外输入参数。
    5. 最后,我们希望将整个调用 sp_session_create 包含在一些代码中,该代码将检查实际返回值并将其映射到指示失败的 Java 异常。为此,我还手动编写了以下异常类:

      public class SpotifyException extends Exception {
        public SpotifyException(String reason) {
          super(reason);
        }
      }
      

    完成所有这些工作后,我们现在可以在 Java 代码中使用它,如下所示:

    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);    
      }
    }
    

    这比原来的界面更简单、更直观,但编写界面更简单。我的倾向是使用 advanced renaming feature 使类型名称也“看起来更像 Java”。

    【讨论】:

    • 你的接口文件有一个错误,pointer_functions(sp_session *, SessionHandle)。删除*。如果存在,我会收到编译错误。但是,删除它时,我不再创建指针。如何解决这个问题
    • 如果%pointer_functions(sp_session *, SessionHandle) 不起作用,则更有可能是sp_session 或您的界面中的其他内容存在问题。我只是仔细检查了一下,该位在 SWIG 2.0.11 中仍然可以正常工作。
    • 谢谢。通过手动编辑 _wrap 源,我可以使它工作。我使用 SWIG 2.0.7.3。我想我会尝试安装更新的 SWIG。双指针从来没有成功过。它插入了一些 const *& 演员表
    【解决方案2】:

    您应该告知 swig 您的 typedef 声明。为了做到这一点,你应该编辑你的接口文件:

    typedef struct sp_session sp_session;
    

    但请注意在 SWIG 看到任何 sp_session 之前通知 SWIG 您的 typedef(我的意思是在包含 API.h 之前)。 我在 typedef 识别方面遇到了同样的问题。也许这个link 会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多