如何用SWIG

时间:2016-10-26 09:45:11

标签: java java-native-interface swig

从这个主题跟进: How should I write the .i file to wrap callbacks in Java or C#

我意识到我的问题很相似,但该问题的答案是针对void*用户数据参数量身定制的,而我的回调则采用枚举和char*

这是我的头文件

中定义和使用回调的方法
typedef void (*Callback_t)(const Log SomeLog, const char *Text);

virtual void SetCallback(const Callback_t SomeCallback, const Log SomeLog) = 0;

其中Log是枚举。

我对JNI和SWIG很新,所以需要一个关于如何包装它的特定指南,类似于我在上面提到的主题中提供的指南。

提前致谢。

1 个答案:

答案 0 :(得分:1)

简单的解决方案是在前一个问题中调整我的答案,只使用全局变量来存储jobject我们无法存储在回调期间传递给我们的某些参数中。 (图书馆作者的设计很糟糕,在设置回调时,在回调发生时,似乎没有一种方法可以传递参数。通常情况下,这是{{{ 1}}或简单地void*。鉴于它是C ++,如果是我设计库我个人使用this然后我们可以简单地依赖SWIG导演,但这似乎不是在这种情况下是一个选项)

为了解决这个问题,我写了test.h:

std::function

和test.c:

typedef enum { 
  blah = 1,
  blahblah = 2,
} Log;

typedef void (*Callback_t)(const Log SomeLog, const char *Text);

void SetCallback(const Callback_t SomeCallback, const Log SomeLog);

void TestIt(const char *str);

(请注意,我正在完成这个C语言练习,因为你必须使用C ++接口,不管怎么说,这对我们来说都是C语言。)

有了这个,你就可以为SWIG编写一个接口文件,如:

#include "test.h"
#include <assert.h>

static Callback_t cb=0;
static Log log=0;

void SetCallback(const Callback_t SomeCallback, const Log SomeLog)  {
  cb = SomeCallback;
  log = SomeLog;
}

void TestIt(const char *str) {
  assert(cb);
  cb(log, str);
}

一般情况下,将1-1映射到earlier answer,但使用全局变量和improving the way we get JNIEnv inside the callback替换%module test %{ #include "test.h" #include <assert.h> // NEW: global variables (bleurgh!) static jobject obj; static JavaVM *jvm; // 2: static void java_callback(Log l, const char *s) { printf("In java_callback: %s\n", s); JNIEnv *jenv = 0; // NEW: might as well call GetEnv properly... const int result = (*jvm)->GetEnv(jvm, (void**)&jenv, JNI_VERSION_1_6); assert(JNI_OK == result); const jclass cbintf = (*jenv)->FindClass(jenv, "Callback"); assert(cbintf); const jmethodID cbmeth = (*jenv)->GetMethodID(jenv, cbintf, "Log", "(LLog;Ljava/lang/String;)V"); assert(cbmeth); const jclass lgclass = (*jenv)->FindClass(jenv, "Log"); assert(lgclass); const jmethodID lgmeth = (*jenv)->GetStaticMethodID(jenv, lgclass, "swigToEnum", "(I)LLog;"); assert(lgmeth); jobject log = (*jenv)->CallStaticObjectMethod(jenv, lgclass, lgmeth, (jint)l); assert(log); (*jenv)->CallVoidMethod(jenv, obj, cbmeth, log, (*jenv)->NewStringUTF(jenv, s)); } %} // 3: %typemap(jstype) Callback_t "Callback"; %typemap(jtype) Callback_t "Callback"; %typemap(jni) Callback_t "jobject"; %typemap(javain) Callback_t "$javainput"; // 4: (modified, not a multiarg typemap now) %typemap(in) Callback_t { JCALL1(GetJavaVM, jenv, &jvm); obj = JCALL1(NewGlobalRef, jenv, $input); JCALL1(DeleteLocalRef, jenv, $input); $1 = java_callback; } %include "test.h" 保留回调信息除外。

使用我们手动编写的Callback.java:

struct

这足以让这个测试用例成功编译并运行:

public interface Callback {
  public void Log(Log log, String str);
}

哪个有效:

public class run implements Callback {
  public static void main(String[] argv) {
    System.loadLibrary("test");
    run r = new run();
    test.SetCallback(r, Log.blah);
    test.TestIt("Hello world");
  }

  public void Log(Log l, String s) {
    System.out.println("Hello from Java: " + s);
  }
}

因为我们不喜欢使用像这样的全局变量(从Java端多次调用swig -Wall -java test.i gcc -Wall -Wextra -o libtest.so -shared -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/ -I/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/linux/ test.c test_wrap.c -fPIC javac *.java && LD_LIBRARY_PATH=. java run In java_callback: Hello world Hello from Java: Hello world 将不会按照我们期望的方式运行)我的首选解决方案(在纯粹的C世界中)是使用libffi为我们生成一个闭包。本质上,我们可以为每个活动回调创建一个新的函数指针,以便每次发生回调时都可以隐式传递调用哪个Java对象的知识。 (这是我们正在努力解决的问题。Libffi has an example of closures非常适合我们的情景。

为了说明这一点,测试用例没有改变,SWIG接口文件就变成了这样:

SetCallback

通过使用libffi创建闭包,我们实现了删除全局变量的目标。 %module test %{ #include "test.h" #include <assert.h> #include <ffi.h> struct Callback { ffi_closure *closure; ffi_cif cif; ffi_type *args[2]; JavaVM *jvm; void *bound_fn; jobject obj; }; static void java_callback(ffi_cif *cif, void *ret, void *args[], struct Callback *cb) { printf("Starting arg parse\n"); Log l = *(unsigned*)args[0]; const char *s = *(const char**)args[1]; assert(cb->obj); printf("In java_callback: %s\n", s); JNIEnv *jenv = 0; assert(cb); assert(cb->jvm); const int result = (*cb->jvm)->GetEnv(cb->jvm, (void**)&jenv, JNI_VERSION_1_6); assert(JNI_OK == result); const jclass cbintf = (*jenv)->FindClass(jenv, "Callback"); assert(cbintf); const jmethodID cbmeth = (*jenv)->GetMethodID(jenv, cbintf, "Log", "(LLog;Ljava/lang/String;)V"); assert(cbmeth); const jclass lgclass = (*jenv)->FindClass(jenv, "Log"); assert(lgclass); const jmethodID lgmeth = (*jenv)->GetStaticMethodID(jenv, lgclass, "swigToEnum", "(I)LLog;"); assert(lgmeth); jobject log = (*jenv)->CallStaticObjectMethod(jenv, lgclass, lgmeth, (jint)l); assert(log); (*jenv)->CallVoidMethod(jenv, cb->obj, cbmeth, log, (*jenv)->NewStringUTF(jenv, s)); } %} // 3: %typemap(jstype) Callback_t "Callback"; %typemap(jtype) Callback_t "long"; %typemap(jni) Callback_t "jlong"; %typemap(javain) Callback_t "$javainput.prepare_fp($javainput)"; // 4: %typemap(in) Callback_t { $1 = (Callback_t)$input; } %typemap(javaclassmodifiers) struct Callback "public abstract class" %typemap(javacode) struct Callback %{ public abstract void Log(Log l, String s); %} %typemap(in,numinputs=1) (jobject me, JavaVM *jvm) { $1 = JCALL1(NewWeakGlobalRef, jenv, $input); JCALL1(GetJavaVM, jenv, &$2); } struct Callback { %extend { jlong prepare_fp(jobject me, JavaVM *jvm) { if (!$self->bound_fn) { int ret; $self->args[0] = &ffi_type_uint; $self->args[1] = &ffi_type_pointer; $self->closure = ffi_closure_alloc(sizeof(ffi_closure), &$self->bound_fn); assert($self->closure); ret=ffi_prep_cif(&$self->cif, FFI_DEFAULT_ABI, 2, &ffi_type_void, $self->args); assert(ret == FFI_OK); ret=ffi_prep_closure_loc($self->closure, &$self->cif, java_callback, $self, $self->bound_fn); assert(ret == FFI_OK); $self->obj = me; $self->jvm = jvm; } return *((jlong*)&$self->bound_fn); } ~Callback() { if ($self->bound_fn) { ffi_closure_free($self->closure); } free($self); } } }; %include "test.h" 现在已成为一个抽象类,其中包含实现它的C和Java组件。它的目标实际上是保持抽象方法Callback的实现,并管理需要保持的其余C数据的生命周期来实现它。大多数libffi工作都在Log SWIG指令内完成,该指令几乎反映了闭包的libffi文档。 %extend函数现在使用传入的userdefined参数来存储它需要的所有信息而不是全局查找,并且必须通过ffi调用传递/接收函数参数。我们的java_callback typemap现在使用我们通过Callback_t添加的额外函数来帮助设置指向我们真正需要的闭包的函数指针。

这里需要注意的一件重要事情是,您在Java端负责管理Callback实例的生命周期,没有办法从C端看到这些信息,因此过早的垃圾收集是一种风险。

要编译并运行它,工作%extend需要在run.java中成为implements,并且编译器需要添加extends。除此之外,它像以前一样工作。

因为在你的实例中被包装的语言是C ++而不是C,我们实际上可以通过依靠SWIG的导演功能来帮助我们一点点来简化一些JNI代码。然后变成:

-lffi

这个.i文件大大简化了%module(directors="1") test %{ #include "test.h" #include <assert.h> #include <ffi.h> %} %feature("director") Callback; // This rename makes getting the C++ generation right slightly simpler %rename(Log) Callback::call; // Make it abstract %javamethodmodifiers Callback::call "public abstract" %typemap(javaout) void Callback::call ";" %typemap(javaclassmodifiers) Callback "public abstract class" %typemap(jstype) Callback_t "Callback"; %typemap(jtype) Callback_t "long"; %typemap(jni) Callback_t "jlong"; %typemap(javain) Callback_t "$javainput.prepare_fp()"; %typemap(in) Callback_t { $1 = (Callback_t)$input; } %inline %{ struct Callback { virtual void call(Log l, const char *s) = 0; virtual ~Callback() { if (bound_fn) ffi_closure_free(closure); } jlong prepare_fp() { if (!bound_fn) { int ret; args[0] = &ffi_type_uint; args[1] = &ffi_type_pointer; closure = static_cast<decltype(closure)>(ffi_closure_alloc(sizeof(ffi_closure), &bound_fn)); assert(closure); ret=ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_void, args); assert(ret == FFI_OK); ret=ffi_prep_closure_loc(closure, &cif, java_callback, this, bound_fn); assert(ret == FFI_OK); } return *((jlong*)&bound_fn); } private: ffi_closure *closure; ffi_cif cif; ffi_type *args[2]; void *bound_fn; static void java_callback(ffi_cif *cif, void *ret, void *args[], void *userdata) { (void)cif; (void)ret; Callback *cb = static_cast<Callback*>(userdata); printf("Starting arg parse\n"); Log l = (Log)*(unsigned*)args[0]; const char *s = *(const char**)args[1]; printf("In java_callback: %s\n", s); cb->call(l, s); } }; %} %include "test.h" 内部所需的代码,现在是以前的libffi和C实现的替代品。几乎所有这些变化都与理智的导演和修复一些C-isms有关。我们现在需要做的就是从回调中调用纯虚拟C ++方法,SWIG已经生成了处理其余部分的代码。