如何使用 SWIG 在 Java 中包装回调
How to wrap callbacks in Java with 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 的新手,因此需要有关如何包装它的具体指南,类似于我在上面提到的线程中提供的指南。
提前致谢。
简单的解决方案是调整我在上一个问题中的回答,只使用一个全局变量来存储 jobject
我们不能存储在回调期间传递给我们的一些参数中。 (库作者的设计很糟糕,似乎没有办法在设置回调时传递参数,回调函数可用,回调发生时。通常是 void*
或简单地 this
。考虑到它是 C++,如果是我设计我个人会使用 std::function
的库,然后我们可以在这里简单地依赖 SWIG 主管,但这似乎不是在这种情况下是一个选项)
为了解决这个问题,我写了 test.h:
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);
和test.c:
#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);
}
(请注意,我正在做这个纯粹是作为 C 中的练习,因为你必须使用的 C++ 接口无论如何对我们来说也可能是 C)。
有了它,您就可以为 SWIG 编写一个接口文件,例如:
%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);
= java_callback;
}
%include "test.h"
一般来说 1-1 映射到 earlier answer, with the exception of replacing the struct
holding callback information with global variables and improving the way we get JNIEnv
inside the callback。
用我们手写的Callback.java:
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);
}
}
哪个有效:
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
因为我们不喜欢像这样使用全局变量(从 Java 端多次调用 SetCallback
不会像我们期望的那样运行)我的首选解决方案(纯粹C world)就是利用libffi为我们生成闭包。从本质上讲,这让我们可以为每个活动回调创建一个新的函数指针,以便在每次回调发生时可以隐式传递正在调用哪个 Java 对象的知识。 (这就是我们正在努力解决的问题。Libffi has an example of closures 非常符合我们的场景。
为了说明这一点,测试用例没有改变,SWIG 接口文件变成了这样:
%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 {
= (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) {
= JCALL1(NewWeakGlobalRef, jenv, $input);
JCALL1(GetJavaVM, jenv, &);
}
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"
它通过使用 libffi 创建闭包实现了我们的 objective 删除全局变量。 Callback
现在变成了一个抽象的 class,混合了 C 和 Java 组件来实现它。它的目标实际上是保持抽象方法 Log
的实现,并管理实现它需要保持的其余 C 数据的生命周期。大多数 libffi 工作是在 %extend
SWIG 指令中完成的,它几乎反映了闭包的 libffi 文档。 java_callback
函数现在使用传入的用户定义参数来存储它需要的所有信息,而不是全局查找,并且必须通过 ffi 调用 pass/receive 函数参数。我们的 Callback_t
类型映射现在使用我们通过 %extend
添加的额外函数来帮助设置指向我们真正需要的闭包的函数指针。
这里要注意的一件重要事情是,您在 Java 端负责管理回调实例的生命周期,没有办法使该信息从 C 端可见,所以为时过早垃圾收集是一种风险。
要编译和 运行 这个工作 implements
需要在 run.java 中变成 extends
并且编译器需要添加 -lffi
。除此之外,它像以前一样工作。
由于在您的实例中被包装的语言是 C++ 而不是 C,我们实际上可以通过依赖 SWIG 的导向器功能来帮助我们稍微简化一些 JNI 代码。然后变成:
%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 {
= (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"
这个 .i 文件极大地简化了 java_callback
内部所需的代码,现在可以替代以前的 libffi 和 C 实现。几乎所有的变化都与明智地启用导演和修复一些 C 主义有关。我们现在需要做的就是从我们的回调中调用纯虚拟 C++ 方法,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 的新手,因此需要有关如何包装它的具体指南,类似于我在上面提到的线程中提供的指南。
提前致谢。
简单的解决方案是调整我在上一个问题中的回答,只使用一个全局变量来存储 jobject
我们不能存储在回调期间传递给我们的一些参数中。 (库作者的设计很糟糕,似乎没有办法在设置回调时传递参数,回调函数可用,回调发生时。通常是 void*
或简单地 this
。考虑到它是 C++,如果是我设计我个人会使用 std::function
的库,然后我们可以在这里简单地依赖 SWIG 主管,但这似乎不是在这种情况下是一个选项)
为了解决这个问题,我写了 test.h:
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);
和test.c:
#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);
}
(请注意,我正在做这个纯粹是作为 C 中的练习,因为你必须使用的 C++ 接口无论如何对我们来说也可能是 C)。
有了它,您就可以为 SWIG 编写一个接口文件,例如:
%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);
= java_callback;
}
%include "test.h"
一般来说 1-1 映射到 earlier answer, with the exception of replacing the struct
holding callback information with global variables and improving the way we get JNIEnv
inside the callback。
用我们手写的Callback.java:
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);
}
}
哪个有效:
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
因为我们不喜欢像这样使用全局变量(从 Java 端多次调用 SetCallback
不会像我们期望的那样运行)我的首选解决方案(纯粹C world)就是利用libffi为我们生成闭包。从本质上讲,这让我们可以为每个活动回调创建一个新的函数指针,以便在每次回调发生时可以隐式传递正在调用哪个 Java 对象的知识。 (这就是我们正在努力解决的问题。Libffi has an example of closures 非常符合我们的场景。
为了说明这一点,测试用例没有改变,SWIG 接口文件变成了这样:
%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 {
= (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) {
= JCALL1(NewWeakGlobalRef, jenv, $input);
JCALL1(GetJavaVM, jenv, &);
}
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"
它通过使用 libffi 创建闭包实现了我们的 objective 删除全局变量。 Callback
现在变成了一个抽象的 class,混合了 C 和 Java 组件来实现它。它的目标实际上是保持抽象方法 Log
的实现,并管理实现它需要保持的其余 C 数据的生命周期。大多数 libffi 工作是在 %extend
SWIG 指令中完成的,它几乎反映了闭包的 libffi 文档。 java_callback
函数现在使用传入的用户定义参数来存储它需要的所有信息,而不是全局查找,并且必须通过 ffi 调用 pass/receive 函数参数。我们的 Callback_t
类型映射现在使用我们通过 %extend
添加的额外函数来帮助设置指向我们真正需要的闭包的函数指针。
这里要注意的一件重要事情是,您在 Java 端负责管理回调实例的生命周期,没有办法使该信息从 C 端可见,所以为时过早垃圾收集是一种风险。
要编译和 运行 这个工作 implements
需要在 run.java 中变成 extends
并且编译器需要添加 -lffi
。除此之外,它像以前一样工作。
由于在您的实例中被包装的语言是 C++ 而不是 C,我们实际上可以通过依赖 SWIG 的导向器功能来帮助我们稍微简化一些 JNI 代码。然后变成:
%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 {
= (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"
这个 .i 文件极大地简化了 java_callback
内部所需的代码,现在可以替代以前的 libffi 和 C 实现。几乎所有的变化都与明智地启用导演和修复一些 C 主义有关。我们现在需要做的就是从我们的回调中调用纯虚拟 C++ 方法,SWIG 已生成处理其余部分的代码。