SWIG接口通过函数参数在Java中接收不透明的结构引用

我正在尝试使用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界面和打字图,但是我没有成功地尝试应用我在文档中找到的示例 。

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

例如给定头文件:

 #include  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  %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程序员会期望从创建者函数返回新的会话句柄对象,并且将使用exception来指示失败。 通过稍微更改接口文件,我们可以使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" 

编号的注释与这些点相对应:

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

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

这比原始的更简单,更直观,但编写界面更简单。 我倾向于使用高级重命名function使类型名称“看起来更像Java”。

你应该告诉swig你的typedef声明。 为此,您应该使用以下命令编辑您的界面文件:

 typedef struct sp_session sp_session; 

但是在看到任何sp_session之前要小心告知SWIG你的typedef(我的意思是在包含API.h之前)。 我对typedef识别有同样的问题。 也许这个链接会有所帮助。