SWIG 支持 void * C 返回类型

发布于 2024-12-16 18:27:26 字数 3153 浏览 1 评论 0原文

我的 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 方法。关于如何处理这个问题有什么想法吗?

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

I have a C function in my SWIG interface file (included from a header file) that currently returns a char * that SWIG uses to generate a String Java return type for fetchFromRow method. I'm wondering if I change the return to a void * on the C side, what will SWIG do on the Java side? The C fetchFromRow function returns a structure (sockaddr_in), String or int. How can I setup my SWIG interface file to support this? Is there a way to make the generated Java fetchFromRow have a return type of Object so that I can just cast on the Java side?

C Code:

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

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

When I generate the method using the void * in the header file (included in SWIG interface file), I get a java method with a SWIGTYPE_p_void return type. Any ideas on how to handle that?

Swig File:

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

Java Code to call fetch:

{
   //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
}

C Code Wrapper that I'm attempting to Replace with SWIG. This use to be called from Java but now I'm trying to call fetch directly (pseudo code):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

深海蓝天 2024-12-23 18:27:26

如果您为返回类型定义了类型层次结构,并使用基类作为 fetchFromRow 的返回类型,这可能会更容易。 (事实证明,解决方案并不像我最初想象的那么容易,甚至还有 文档中的示例这个问题也适用于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;
  }
}

这里我们有四种不同的类型,与 配对>枚举值。选择这些是为了简单起见。它们可以是您想要的任何内容,只要您有可以应用的合适的类型映射。 (如果您特别想要 sockaddr_in ,则应用 我对你之前关于专门包装 sockaddr_in 的问题的回答)。

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

为了包装它,我们需要一个 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 typemap)

每个构造函数的第二个参数,此处视为 $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 端,所有指针都表示为。它确保函数与 JNI 调用返回的内容完全兼容,而不会丢失精度(在某些平台上,jlong​​ 可能是 long long)。基本上,这些函数的存在是为了以尽可能少的手动工作实现从通用 (void*) 到 C 端特定函数的转换。 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

This might be easier if you defined a type hierarchy for the return types, and used the base class as the return type for fetchFromRow. (It turns out that solution isn't as easy as I originally thought it would be and there's even an example in the documentation! This question also applies to Java+SWIG) As it stands though doing what you asked is possible, I've made a simpler example to illustrate the important points.

The example I'm working with here has a simple interface, in test.h (both the declarations and the definitions to keep things simpler here):

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

Here we have four different types, paired with an enum value. These were picked for simplicity. They could be anything you want, provided you have a suitable typemap that can be applied. (If you want sockaddr_in specifically then apply the typemap from my answer to your previous question about wrapping sockaddr_in specifically).

There's also a function fetch which creates one of the types and returns it via void *. This illustrates what you asked about in the question. The tricky thing with fetch is that SWIG has no direct way of inferring what was behind the void* pointer before it got returned. We need to give SWIG a way of knowing more specifically what the type is, using our higher level knowledge about the meaning of the type_t parameter.

To wrap this we need a SWIG interface file, test.i which begins with the usual module stuff:

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

In order to wrap our fetch function we need to find a sensible way of exposing the closest thing to void* on the Java side. In this instance I think java.lang.Object is a good choice for the return type and it approximates the void* reasonably well here.

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

This sets up what the return type from fetch on the Java side will be. (We've not changed the return type of the generated JNI intermediary using %typemap(jtype) though, this will still default to long as before).

Next we need to write another typemap to specify how the result of the actual call is going to get converted into the type we said the call would return on the Java side:

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

Here we create a Java object, of the appropriate the SWIG proxy types, or call a helper function we'll see shortly. We decide which to type to use by looking at the type_t that was used in the call. (I'm not 100% happy about referring to this type by name, i.e. type directly but there doesn't seem to be a better way of gaining access to the parameters a function was called with inside a javaout typemap)

The second argument to each of the constructors, seen here as $owner is important for memory management, it states who owns the allocation and we'd like to transfer ownership to Java to avoid leaking it. Its value is determined by the %newobject directive.

We need a helper function to convert the void* to more meaningful types for the int32_t and String cases. We supply this with %inline to ask SWIG to wrap and define it at the same time:

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

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

I used jlong here because on the Java side of the interface all pointers are represented as long. It ensures the functions are exactly compatible with what the JNI call is returning, without losing precision (on some platforms it's possible that jlong might be a long long). Basically these functions exist to make the conversion from the generic (void*) to the specific on the C side with as little manual work as possible. SWIG handles all of the types for us here.

Finally we finish up with %include for the header file (we want to wrap it all so that's the simplest way) and some code to cause the library to get loaded automatically:

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

I tested this wrapping by compiling:

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 

and running the following Java code to "exercise" it a bit.

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

You can always recreate the whole example from the code shown here, test.i is shown and discussed sequentially, but for convenience I've also put a copy of test.i on my site.

放飞的风筝 2024-12-23 18:27:26

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

祝你好运。

The approach I like to use for tough cases like this one is to (a) simplify the C interface to get better results from SWIG, and (b) bring the complexity back in the target language as a wrapper to the simplified C functions.

In your case, I would add two additional functions to the C API:

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

These would be a piece of cake for SWIG to handle. Then on the Java side you can recreate the original fetchFromRow(), where the implementation calls the String or Int version of the C function depending on the type, and finally returns the result as Object.

Good luck.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文