【问题标题】:SWIG Support of void * C return typesSWIG 支持 void * C 返回类型
【发布时间】:2011-11-17 20:01:49
【问题描述】:

我的 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);

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

痛饮文件:

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

【问题讨论】:

  • 我认为这是stackoverflow.com/questions/2345354/…的骗子
  • 略有不同。它关于支持 void C 返回类型
  • 这很棘手,但我认为可行。基本上,您需要在type_ts 和它们所代表的“真实”类型之间进行映射,以使 Java 方面的演员表可行。我有一个类似 Python 的示例,希望本周末能够将其转换为 Java。
  • @MiguelGarcia - 我不认为这是重复的,因为在这种情况下void* 被用作缓冲区。

标签: java java-native-interface swig


【解决方案1】:

如果您为返回类型定义了类型层次结构,并使用基类作为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

接下来我们需要编写另一个类型映射来指定实际调用的结果将如何转换为我们所说的调用将在 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,但似乎没有更好的方法来访问在 javaout 类型映射中调用函数的参数)

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

对于int32_tString 情况,我们需要一个辅助函数来将void* 转换为更有意义的类型。我们向它提供 %inline 以要求 SWIG 同时包装和定义它:

%inline %{
int32_t void2int(jlong v) {
  return (intptr_t)v;
}

const char *void2str(jlong v) {
  return (const char*)v;
}
%}

我在这里使用了jlong,因为在接口的Java 端,所有指针都表示为long。它确保函数与 JNI 调用返回的内容完全兼容,而不会丢失精度(在某些平台上,jlong 可能是 long long)。基本上,这些函数的存在是为了在 C 端以尽可能少的手动工作从通用 (void*) 转换为特定的。 SWIG 在这里为我们处理所有类型。

最后,我们完成了头文件的%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);
    }
  }
%}

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

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 

并运行以下 Java 代码来“锻炼”一下。

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 按顺序显示和讨论,但为方便起见,我还放了一份 test.i on my site 的副本。

【讨论】:

  • 我再次查看了这个答案,只是想知道是否有办法在不修改头文件的情况下处理 void* ?我假设不是......只是想我会问。
  • @c12 - 我给出的使用 javaout 和 jstype 类型映射的答案不需要对头文件进行任何修改即可工作。我展示的头文件只是我对您的头文件的简化版本可能是什么样子的猜测,并给了我一些可以测试的东西。 enum 的类型和 fetch 的实现几乎可以是您喜欢的任何类型。
  • 难道你不能也只是通过 SWIG %apply 将 void* 映射到一个字节数组,然后在 Java 中只处理字节数组并将其转换为字符串、整数..等吗?不确定它的实现,但听起来可行......想法?
  • @c12 - 这是可行的,但对我来说似乎需要做很多工作,因为我们可以要求 SWIG 为我们包装所有类型并且只需编写一点代码来进行通道返回到正确的那个。以另一种方式执行此操作将需要为您想要支持的每种类型编写大量 Java 并制作它们的副本,而不是对原始 C 或 C++ 类型的引用。我怀疑将byte[] 恰好是C++ 中的int32_t 转换为Java 中的整数将是相当棘手的。 SWIG 隐藏了这一切。 JNI 只是将 int 转换为 byte[] 会很乏味
  • 我同意你的看法。我提出这个的原因是我知道 Java,而 SWIG 对我来说是全新的。我会尝试让您的建议发挥作用,因为它是最干净的解决方案。
【解决方案2】:

对于像这样的棘手情况,我喜欢使用的方法是 (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的形式返回结果。

祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-28
    • 2011-04-08
    • 2012-07-10
    • 1970-01-01
    • 2016-06-29
    • 1970-01-01
    • 2015-06-29
    相关资源
    最近更新 更多