在生成的接口中公开void指针

时间:2012-07-18 06:41:10

标签: java java-native-interface swig

我使用SWIG为Java中的Spotify library生成包装器。 头文件包含

typedef struct sp_session_config {
   ...
  const void *application_key;           ///< Your application key
  ...

应该允许我在Java中设置应用程序密钥:

sp_session_config cfg = new sp_session_config();
cfg.setApplication_key(appkey);

C中的键声明如下

const uint8_t g_appkey[] = {0x01, 0xB4, 0xF9, 0x33, .... }

问题是我不知道如何在Java中创建密钥。 appkey的类型必须为SWIGTYPE_p_void

我尝试添加.i文件:

%include "carrays.i"
%array_functions(uint8_t, uint8Array);

能够使用以下函数在Java中创建C数组:

new_uint8Array
uint8Array_setitem

没有成功。

如何解决我的问题?我们欢迎一个简单的小例子。

1 个答案:

答案 0 :(得分:1)

你使用carrays.i就行了,尽管通常我更喜欢在给出选择时使用%array_class代替%array_functions。问题是你的array_functions / array_class给你一个SWIGTYPE_p_unsigned_char(即代表指向unsigned char的代理),而你需要一个void指针,因为它没有琐碎的方法在两者之间进行转换Java中的强类型。我们可以解决这个问题。

为了说明这个答案,我创建了一个非常简单的头文件来重现你的问题:

typedef struct sp_session_config {
  const void *application_key;           ///< Your application key
} session_config;

所以问题是我们无法使用array_class / array_function生成的setApplication_key()数组调用uint8_t,因为类型不匹配。

有很多可能的解决方案。一种方法可能是告诉SWIG忽略application_key中的sp_session_config,而是假装它是uint8_t* application_key。为此,您需要在C中为执行工作的包装器提供set / get函数(在这种情况下是微不足道的),并使用%ignore隐藏“真实”成员%extend以添加“假”成员和%rename阻止%ignore忽略假冒成员:

%module test

%{
#include "test.h"
#include <stdint.h>

// Internal helpers for our fake memeber
static void session_config_application_key_set(session_config *cfg, const uint8_t *arr) {
  cfg->application_key = arr;
}

static const uint8_t *session_config_application_key_get(const session_config *cfg) {
  return cfg->application_key;
}
%}

%include <carrays.i>
%include <stdint.i>

%array_class(uint8_t, uint8Array);

%ignore sp_session_config::application_key; // ignore the real one when we see it
%include "test.h"
%rename("%s") sp_session_config::application_key; // unignore for extend
%extend session_config {
  uint8_t *application_key;
}

这足以让我们用Java编写:

public class run {
  public static void main(String[] argv) {
    session_config cfg = new session_config();
    uint8Array key = new uint8Array(4);
    key.setitem(0, (short)100); key.setitem(1, (short)101); 
    key.setitem(2, (short)102); key.setitem(3, (short)103);    
    cfg.setApplication_key(key.cast());
  }
}

或者,如果您宁愿使用typemap系统将成员公开为uint8_t*,那么:

%module test

%{
#include "test.h"
%}

%include <carrays.i>
%include <stdint.i>

%array_class(uint8_t, uint8Array);

%typemap(jstype) const void *application_key "$typemap(jstype, const uint8_t*)"
%typemap(javain) const void *application_key "$typemap(jstype, const uint8_t*).getCPtr($javainput)"
%typemap(javaout) const void *application_key {
  long cPtr = $jnicall;
  return (cPtr == 0) ? null : new $typemap(jstype, const uint8_t*)(cPtr, $owner);
}
%include "test.h"

仅匹配const void *application_key并在界面中生成代码以使用包装的uint8_t*而不是void*

另一种可能的方法是通过在接口文件中提供uint8_t*的特殊定义,简单地向SWIG声称成员 struct

%module test

%{
#include "test.h"
%}

%include <carrays.i>
%include <stdint.i>

%array_class(uint8_t, uint8Array);

typedef struct sp_session_config {
  const uint8_t *application_key;
} session_config;

// Ignore the one in the header file, use our special version instead
%ignore sp_session_config;
%include "test.h"

这样做很简单,但维护起来比较棘手 - 每次sp_session_config更改时,您都必须确保更新接口文件以匹配它,否则您可能不会暴露,甚至错误地暴露{ {1}}。

请注意:请注意这些示例中的所有权 - Java端的数组保留所有权,因此您需要确保:

  1. C库不会尝试释放它(如果有的话,你会看到一个双struct
  2. Java中数组的生命周期超过了C库中的使用范围。
  3. 如果您愿意,可以让C库获得内存的所有权,这些示例中最简单的方法是使用类型映射。 (您可以修改javain类型映射以获取所有权)。

    最后,对于另一种可能的解决方案,您可以使用我在过去回答的a little bit of JNIyet more other techniques来使用Java数组或为此生成合适的代码。