要始终自动将相同的指针传递给函数,在 SWIG 中相当简单。例如,给出“头”文件 test.h,它捕获了问题的核心部分:
struct context; // only used for pointers
void init_context(struct context **ctx) { *ctx=malloc(1); printf("Init: %p\n", *ctx); }
void release_context(struct context *ctx) { printf("Delete: %p\n", ctx); free(ctx); }
void foo(struct context *ctx) { printf("foo: %p\n", ctx); }
我们可以包装它并自动导致全局上下文传递到任何需要的地方,方法如下:
%module test
%{
#include "test.h"
// this code gets put in the generated C output from SWIG, but not wrapped:
static struct context *get_global_ctx() {
static struct context *ctx = NULL;
if (!ctx)
init_context(&ctx);
return ctx;
}
%}
%typemap(in,numinputs=0) struct context *ctx "$1=get_global_ctx();"
%ignore init_context; // redundant since we call it automatically
%include "test.h"
这设置了一个类型映射struct context *ctx
不是从 Java 获取输入,而是自动调用get_global_ctx()
任何地方都匹配。
这可能足以为 Java 开发人员提供一个健全的接口,但它并不理想:它强制上下文成为全局的,并且意味着没有 Java 应用程序可以同时使用多个上下文。
鉴于 Java 是一种面向对象语言,一个更好的解决方案是使上下文成为第一类对象。我们也可以让 SWIG 为我们生成这样的接口,尽管它有点复杂。我们的 SWIG 模块文件变为:
%module test
%{
#include "test.h"
%}
// These get called automatically, no need to expose:
%ignore init_context;
%ignore delete_context;
// Fake struct to convince SWIG it should be an object:
struct context {
%extend {
context() {
// Constructor that gets called when this object is created from Java:
struct context *ret = NULL;
init_context(&ret);
return ret;
}
~context() {
release_context($self);
}
}
};
%include "test.h"
我们可以成功地执行这段代码:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
context ctx = new context();
// You can't count on the finalizer if it exits:
ctx.delete();
ctx = null;
// System.gc() might also do the trick and in a longer
// running app it would happen at some point probably.
}
}
gives:
Init: 0xb66dab40
Delete: 0xb66dab40
在动态类型语言中,这将是最难完成的部分 - 我们可以使用一种或另一种形式的元编程来根据需要插入成员函数。因此我们可以说类似的话new context().foo();
完全符合预期。 Java 是静态类型的,所以我们还需要更多东西。我们可以通过多种方式在 SWIG 中做到这一点:
接受我们现在可以打电话test.foo(new context());
很高兴 - 它看起来仍然很像 Java 中的 C,所以我建议如果你最终编写了很多看起来像 C 的 Java,这可能是一种代码味道。
-
Use %extend
(手动)将方法添加到上下文类中,%extend
在 test.i 中变为:
%extend {
context() {
// Constructor that gets called when this object is created from Java:
struct context *ret = NULL;
init_context(&ret);
return ret;
}
~context() {
release_context($self);
}
void foo() {
foo($self);
}
}
-
As with %extend
,但是使用类型映射在 Java 端编写胶水:
%typemap(javacode) struct context %{
public void foo() {
$module.foo(this);
}
%}
(注意:这需要在接口文件中足够早才能工作)
请注意,我在这里没有向 SWIG 展示我的上下文结构的真正定义 - 它always对于需要真正定义的任何内容,都遵循我的“库”,因此不透明指针仍然完全不透明。
一个更简单的解决方案来包装init_context
使用双指针将使用%inline
提供仅在包装器中使用的额外函数:
%module test
%{
#include "test.h"
%}
%inline %{
struct context* make_context() {
struct context *ctx;
init_context(&ctx);
return ctx;
}
%}
%ignore init_context;
%include "test.h"
足以让我们编写以下Java:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
// This object behaves exactly like an opaque pointer in C:
SWIGTYPE_p_context ctx = test.make_context();
test.foo(ctx);
// Important otherwise it will leak, exactly like C
test.release_context(ctx);
}
}
替代但类似的方法包括使用cpointer.i 库:
%module test
%{
#include "test.h"
%}
%include <cpointer.i>
%pointer_functions(struct context *,context_ptr);
%include "test.h"
然后您可以将其用作:
public class run {
public static void main(String[] argv) {
System.loadLibrary("test");
SWIGTYPE_p_p_context ctx_ptr = test.new_context_ptr();
test.init_context(ctx_ptr);
SWIGTYPE_p_context ctx = test.context_ptr_value(ctx_ptr);
// Don't leak the pointer to pointer, the thing it points at is untouched
test.delete_context_ptr(ctx_ptr);
test.foo(ctx);
// Important otherwise it will leak, exactly like C
test.release_context(ctx);
}
}
还有一个pointer_class
比这个更面向对象的宏可能值得使用。但要点是,您提供了处理不透明指针对象的工具,SWIG 使用这些对象来表示它一无所知的指针,但避免了getCPtr()
本质上颠覆类型系统的调用。