SWIG 支持 void * C 返回类型
我的 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果您为返回类型定义了类型层次结构,并使用基类作为
fetchFromRow
的返回类型,这可能会更容易。 (事实证明,解决方案并不像我最初想象的那么容易,甚至还有 文档中的示例!这个问题也适用于Java+SWIG)虽然按照您的要求进行操作是可能的,但我做了一个更简单的示例来说明要点。我在这里使用的示例在
test.h
中有一个简单的接口(声明和定义都是为了让事情变得更简单):这里我们有四种不同的类型,与
配对>枚举
值。选择这些是为了简单起见。它们可以是您想要的任何内容,只要您有可以应用的合适的类型映射。 (如果您特别想要sockaddr_in
,则应用 我对你之前关于专门包装sockaddr_in
的问题的回答)。还有一个函数
fetch
,它创建其中一种类型并通过void *
返回它。这说明了您在问题中询问的内容。fetch
的棘手之处在于,SWIG 在返回之前无法直接推断void*
指针后面的内容。我们需要为 SWIG 提供一种方法,使用我们对type_t
参数含义的更高级别的了解,更具体地了解类型是什么。为了包装它,我们需要一个 SWIG 接口文件
test.i
,它以常见的模块内容开头:为了包装我们的
fetch
函数,我们需要找到一种合理的方法在 Java 端公开与void*
最接近的东西。在这种情况下,我认为java.lang.Object
是返回类型的不错选择,并且它在这里相当好地近似void*
。这设置了 Java 端
fetch
的返回类型。 (尽管我们没有使用%typemap(jtype)
更改生成的 JNI 中介的返回类型,但它仍然像以前一样默认为long
)。接下来,我们需要编写另一个类型映射来指定实际调用的结果如何转换为我们所说的调用将在 Java 端返回的类型:
这里我们创建一个具有适当 SWIG 代理类型的 Java 对象,或者调用我们很快就会看到的辅助函数。我们通过查看调用中使用的
type_t
来决定使用哪个类型。 (我并不是 100% 乐意通过名称直接引用此类型,即直接使用type
,但似乎没有更好的方法来访问在函数内部调用的参数javaout
typemap)每个构造函数的第二个参数,此处视为
$owner
对于内存管理很重要,它说明谁拥有分配以及我们想要转移所有权归 Java 以避免泄露。它的值由%newobject
指令确定。对于
int32_t
和String
情况,我们需要一个辅助函数将void*
转换为更有意义的类型。我们用%inline
提供它,要求 SWIG 同时包装和定义它:我在这里使用
jlong
因为在接口的 Java 端,所有指针都表示为长
。它确保函数与 JNI 调用返回的内容完全兼容,而不会丢失精度(在某些平台上,jlong
可能是long long
)。基本上,这些函数的存在是为了以尽可能少的手动工作实现从通用 (void*
) 到 C 端特定函数的转换。 SWIG 在这里为我们处理所有类型。最后,我们完成了头文件的
%include
(我们希望将其全部包装起来,这是最简单的方法)和一些导致库自动加载的代码:我通过编译测试了这种包装:
并运行以下 Java 代码来“锻炼”一下它。
您始终可以从此处显示的代码重新创建整个示例,
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):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 wantsockaddr_in
specifically then apply the typemap from my answer to your previous question about wrappingsockaddr_in
specifically).There's also a function
fetch
which creates one of the types and returns it viavoid *
. This illustrates what you asked about in the question. The tricky thing withfetch
is that SWIG has no direct way of inferring what was behind thevoid*
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 thetype_t
parameter.To wrap this we need a SWIG interface file,
test.i
which begins with the usual module stuff:In order to wrap our
fetch
function we need to find a sensible way of exposing the closest thing tovoid*
on the Java side. In this instance I thinkjava.lang.Object
is a good choice for the return type and it approximates thevoid*
reasonably well here.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 tolong
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:
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 ajavaout
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 theint32_t
andString
cases. We supply this with%inline
to ask SWIG to wrap and define it at the same time:I used
jlong
here because on the Java side of the interface all pointers are represented aslong
. It ensures the functions are exactly compatible with what the JNI call is returning, without losing precision (on some platforms it's possible thatjlong
might be along 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:I tested this wrapping by compiling:
and running the following Java code to "exercise" it a bit.
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.对于像这样的棘手情况,我喜欢使用的方法是 (a) 简化 C 接口以从 SWIG 获得更好的结果,以及 (b) 将复杂性带回到目标语言中,作为简化 C 函数的包装器。
对于您的情况,我会向 C API 添加两个附加函数:
这对于 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:
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.