SWIG支持void * C返回类型

时间:2011-11-17 20:01:50

标签: java java-native-interface swig

我的SWIG接口文件(包含在头文件中)中有一个C函数,它当前返回一个char *,SWIG用它为fetchFromRow方法生成String Java返回类型。我想知道我是否将C的返回更改为void *,SWIG会在Java端做什么? C fetchFromRow函数返回一个结构(sockaddr_in),String或int。如何设置SWIG接口文件以支持此功能?有没有办法让生成的Java fetchFromRow具有返回类型的Object,以便我可以在Java端进行转换?

C代码:

extern char *
fetchFromRow(struct row_t *r_row,
        type_t type);

extern void *
fetchFromRow(struct row_t *r_row,
        type_t type);

当我在头文件中使用void *生成方法(包含在SWIG接口文件中)时,我得到一个带有SWIGTYPE_p_void返回类型的java方法。关于如何处理的任何想法?

Swig文件:

%module Example
%include "typemaps.i"
%include "stdint.i"
%include "arrays_java.i"
void setPhy_idx(uint32_t value);
%include "arrays_java.i"
void setId(unsigned char *value);
%{
#include "Example1.h"
#include "Example2.h"
#include "Example3.h"
#include "Example4.h"
%}
%rename setLogFile setLogFileAsString;
%inline %{
void setLogFileAsString(const char *fn) {
  FILE *f = fopen(fn, "w");
  setLogFile(f);
}
%}
%typemap(jstype) void* "java.lang.Object"
%typemap(javaout) void* {
  long cPtr = $jnicall;
  Object result = null;
  if (type == type_t.TYPE_CATEGORY) {
    result = void2int8(cPtr);
  }
  return result;
}

%newobject fetch(struct result_row_t *result_row, type_t type, int32_t *length);
%inline %{

uint8_t void2int8(jlong v) {
  return (intptr_t)v;
}

%}
%apply char * { unsigned char * };
%apply char * { const void * }
%apply int32_t { int32_t * }
int createKey(const void* secret, int secret_len, const sdr_id_t *id, unsigned char key[20], int key_len);
session_t* createSession(const char* addr, int ort, const char *ddr, int rt, const der_id_t *id, unsigned char key[20], int key_len); 
%apply int32_t *OUTPUT { int32_t *length }
%ignore createKey;
%ignore setLogFile;
%ignore ecreateSession;
%include "Example1.h"
%include "Example2.h"
%include "Example3.h"
%include "Example4.h"
%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("Example");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}

调用fetch的Java代码:

{
   //only type_t param shown here for simplicity
   Object category = Example.fetch(result_row, type_t.TYPE_CATEGORY, length);
   System.out.println("category=" + aLevel.toString());
   System.out.println("category=" + ((Short)aLevel).intValue());
   System.out.println("category=" + ((Short)aLevel).toString());
   //all 3 Sys outs show same value but when called again each show same value but different than the first execution
}

我试图用SWIG替换的C代码包装器。这个用法是从Java调用的,但现在我试图直接调用fetch(伪代码):

char *
wrapFetch(struct row_t *result_row, type_t type)
{
int8_t *int8_valp = NULL;
switch (attribute) {
 case TYPE_CATEGORY:

        int8_valp = fetch(
                result_row, attribute, &length);
        if (length > 0 && int8_valp != NULL) {
            snprintf(smallbuf, sizeof(smallbuf), "%u", *int8_valp);
            return strdup(smallbuf);
        } else {
            return NULL;
        }
}

2 个答案:

答案 0 :(得分:3)

如果为返回类型定义了类型层次结构,并且使用基类作为fetchFromRow的返回类型,则这可能会更容易。 (事实证明,解决方案并不像我原先想象的那么容易,甚至还有an example in the documentationThis question also applies to Java+SWIG)尽管做了你所要求的事情,但我做得更简单了举例来说明重点。

我正在使用的示例在test.h中有一个简单的接口(声明和定义都是为了让事情变得更简单):

struct type1 {
  type1(int foo) : foo(foo) {}
  int foo;
};

struct type2 {
  type2(double bar) : bar(bar) {}
  double bar;
};

// TYPE3 is int32_t, TYPE4 is const char*

typedef enum { TYPE1, TYPE2, TYPE3, TYPE4 } type_t;

void* fetch(type_t type) {
  switch(type) {
  case TYPE1:
    return new type1(101);
  case TYPE2:
    return new type2(1.111);
  case TYPE3:
    return (void*)(-123); // One way of returning int32_t via void*!
  case TYPE4:
    return (void*)("Hello world"); // Cast because not const void*
  default:
    return NULL;
  }
}

这里我们有四种不同的类型,与enum值配对。这些是为了简单而选择的。如果您有合适的可应用的类型图,它们可以是您想要的任何内容。 (如果您需要sockaddr_in专门,请应用my answer to your previous question中的类型图专门用于包装sockaddr_in

还有一个函数fetch,它创建一个类型并通过void *返回它。这说明了您在问题中询问的内容。 fetch的棘手问题是SWIG没有直接的方法来推断void*指针返回之前的内容。我们需要使用我们关于type_t参数含义的更高级别知识,为SWIG提供更具体地了解类型的方法。

要包装它,我们需要一个SWIG接口文件test.i,它以通常的模块开头:

%module test
%{
#include "test.h"
%}

为了包装我们的fetch函数,我们需要找到一种在Java端将最接近的事物暴露给void*的合理方法。在这个例子中,我认为java.lang.Object是返回类型的一个很好的选择,它在这里合理地近似了void*

%typemap(jstype) void* "java.lang.Object"

这将设置Java端fetch的返回类型。 (我们没有使用%typemap(jtype)更改生成的JNI中介的返回类型,但仍然会像以前一样默认为long

接下来我们需要编写另一个typemap来指定实际调用的结果将如何转换为我们所说的调用将在Java端返回的类型:

%typemap(javaout) void* {
  long cPtr = $jnicall;
  Object result = null;
  if (type == type_t.TYPE1) {
    result = new type1(cPtr, $owner);
  }
  else if (type == type_t.TYPE2) {
    result = new type2(cPtr, $owner);
  }
  else if (type == type_t.TYPE3) {
    result = void2int(cPtr);
    // could also write "result = new Integer(void2int(cPtr));" explicitly here
  }
  else if (type == type_t.TYPE4) {
    result = void2str(cPtr);
  }

  return result;
}

%newobject fetch(type_t type);

这里我们创建一个适当的SWIG代理类型的Java对象,或者调用我们很快就会看到的辅助函数。我们通过查看调用中使用的type_t来决定要使用哪个类型。 (我对于通过名称直接引用此类型并不是100%感到高兴,即直接type,但似乎没有更好的方法来获取对函数在{{1}内调用的参数的访问权限} typemap)

每个构造函数的第二个参数,在这里看作javaout对于内存管理很重要,它说明谁拥有分配,我们想将所有权转移到Java以避免泄漏它。其值由$owner指令确定。

我们需要一个辅助函数来将%newobject转换为void*int32_t个案例的更有意义的类型。我们提供String来请求SWIG同时包装和定义它:

%inline

我在这里使用%inline %{ int32_t void2int(jlong v) { return (intptr_t)v; } const char *void2str(jlong v) { return (const char*)v; } %} ,因为在接口的Java端,所有指针都表示为jlong。它确保函数与JNI调用返回的内容完全兼容,而不会丢失精度(在某些平台上,long可能是jlong)。基本上这些函数用于通过尽可能少的手动工作从泛型(long long)到C侧的特定转换。 SWIG在这里为我们处理所有类型。

最后,我们以头文件void*结束(我们希望将其全部包装起来,这是最简单的方法)以及一些使库自动加载的代码:

%include

我通过编译测试了这个包装:

%include "test.h"

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("test");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}

并运行以下Java代码来“练习”它。

swig -Wall -java -c++ test.i
javac *.java 
g++ -Wall -Wextra test_wrap.cxx -shared -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux/ -o libtest.so 

您可以随时从此处显示的代码中重新创建整个示例,public class main { public static void main(String[] argv) { Object o1 = test.fetch(type_t.TYPE1); Object o2 = test.fetch(type_t.TYPE2); Object o3 = test.fetch(type_t.TYPE3); Object o4 = test.fetch(type_t.TYPE4); if (!(o1 instanceof type1)) { System.out.println("Wrong type - o1"); } else { System.out.println("o1.getFoo(): " + ((type1)o1).getFoo()); } if (!(o2 instanceof type2)) { System.out.println("Wrong type - o2"); } else { System.out.println("o2.getFoo(): " + ((type2)o2).getBar()); } if (!(o3 instanceof Integer)) { System.out.println("Wrong type - o3"); } else { System.out.println("o3.intValue(): " + ((Integer)o3).intValue()); } if (!(o4 instanceof String)) { System.out.println("Wrong type - o4"); } else { System.out.println("o4: " + (String)o4); } } } 会按顺序显示和讨论,但为了方便起见,我还提供了test.i on my site的副本。

答案 1 :(得分:1)

我喜欢用于这类困难案例的方法是(a)简化C接口以从SWIG获得更好的结果,以及(b)将复杂性带回目标语言作为简化C的包装器功能

在您的情况下,我会向C API添加两个附加功能:

char* fetchFromRowString(struct row_t *r_row);
int fetchFromRowInt(struct row_t *r_row);

对于SWIG来说,这些将是一块蛋糕。然后在Java端,您可以重新创建原始fetchFromRow(),其中实现根据类型调用C函数的String或Int版本,最后将结果作为Object返回。

祝你好运。