如何使用SWIG在Java中包装回调 [英] How to wrap callbacks in Java with SWIG

查看:48
本文介绍了如何使用SWIG在Java中包装回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此线程之后: 应该如何我编写了.i文件,将回调文件包装在Java或C#中.

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

I realize that my question is similar but the answer to that question was tailored specific to void* user data argument, while my callback takes an enum and a char*.

这是在我的头文件中定义和使用我的回调的方式

This is how my callback is defined and used in my header file

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

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

其中Log是一个枚举.

我对JNI和SWIG还是很陌生,因此需要一个具体的包装方法指南,类似于我上面提到的线程中提供的指南.

I'm rather new to JNI and SWIG, so would need a specific guide on how to wrap this, similar to the one presented in the thread I mentioned above.

谢谢.

推荐答案

简单的解决方案是使我对上一个问题的回答适应于仅使用全局变量来存储jobject,而我们无法将其存储在某些在回调过程中传递给我们. (对于库作者而言,这是一个糟糕的设计,在设置回调时,似乎没有一种传递参数的方法,该回调可在函数发生时在函数中使用.通常,这是或只是this.如果是我自己设计的库,那是C ++,那我个人会使用过std::function,那么我们可以在这里简单地依靠SWIG Director,但这似乎不是一个选择.场景)

The simple solution is to adapt my answer at the previous question to just use a global variable to store the jobject we can't store inside some argument that gets passed to us during the callback. (It's bad design on the part of the library author that there doesn't seem to be a way of passing an argument in when setting the callback which is made available to the function, at the time a callback happens. Normally that's either void* or simply this. Given that it's C++ if it were me designing the library I'd have used std::function personally and then we could simply rely on SWIG directors here, but that doesn't seem to be an option in this scenario)

为此,我编写了test.h:

To work through this I wrote 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进行此练习).

(Note that I'm working through this purely as an exercise in C since the C++ interface you've got to work with might as well be C to us here anyway).

安装该文件后,您可以为SWIG编写一个接口文件,如下所示:

With that in place you can then write an interface file for SWIG like:

%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"

通常将1-1映射到改进了在回调中获取JNIEnv的方式.

In general that maps 1-1 onto the 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:

With our manually written Callback.java:

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

足以使此测试用例编译并成功运行:

That's enough for this test case to compile and run successfully:

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语言环境中)是使用libffi来为我们带来关闭.从本质上讲,这使我们可以为每个活动的回调创建一个新的函数指针,以便可以在每次发生回调时隐式传递有关调用哪个Java Object的知识. (这是我们正在努力解决的问题. Libffi的闭包示例非常符合我们的情况.


Since we don't like using global variables like this (multiple calls to SetCallback from the Java side will not behave the way we'd expect) my preferred solution (in a purely C world) is to use libffi to generate a closure for us. Essentially that lets us create a new function pointer for each active callback, so that the knowledge of which Java Object is being called can be implicit passed around each time a callback occurs. (This is what we're struggling to solve. Libffi has an example of closures that fits our scenario fairly closely.

为了说明这一点,测试用例保持不变,SWIG接口文件已变为:

To illustrate this the test case is unchanged, the SWIG interface file has become this:

%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"

通过使用libffi创建闭包,已经达到了删除全局变量的目的. Callback现在已成为抽象类,实现了C和Java组件的混合.实际上,它的目标是保留抽象方法Log的实现,并管理实现此方法需要保留的其余C数据的生命周期. libffi的大部分工作都在%extend SWIG指令内完成,该指令几乎反映了libffi文档中的闭包. java_callback函数现在使用传入的用户定义参数来存储其所需的所有信息,而不是全局查找,并且必须通过ffi调用来传递/接收函数参数.现在,我们的Callback_t类型映射利用了通过%extend添加的额外功能来帮助设置指向我们真正需要的闭包的功能指针.

Which has achieved our objective of removing globals by creating a closure using libffi. Callback has now become an abstract class, with a mix of C and Java components implementing it. The goal of it really is to hold the implementation of the abstract method Log and manage the lifecycle of the rest of the C data which needs to be held to implement this. Most of the libffi work is done inside a %extend SWIG directive, which pretty much mirrors the libffi documentation for closures. The java_callback function now uses the userdefined argument it gets passed in to store all of the information it needs instead of the global lookups, and has to pass/receive the function arguments via the ffi call. Our Callback_t typemap now makes use of the extra function we added via %extend to assist in setting up the function pointer to the closure that we really need.

这里要注意的一件事是,您需要负责管理Callback实例的生命周期的Java端,无法从C端看到该信息,因此过早进行垃圾回收是一种风险.

One important thing to notice here is that you're responsible on the Java side for managing the lifecycle of the Callback instances, there is no way to make that information visible from the C side, so premature garbage collection is a risk.

要编译并运行此程序,必须将工作implements变为run.java中的extends,并且编译器需要添加-lffi.除此之外,它仍然像以前一样工作.

To compile and run this the work implements needs to become extends in run.java and the compiler needs to have -lffi added. Other than that it works as before.

由于在您的实例中,所包装的语言是C ++而不是C,我们实际上可以依靠SWIG的Director功能稍微简化一些JNI代码.然后变成:

Since in your instance the language being wrapped is C++ not C we can actually simplify some of the JNI code a little by relying on SWIG's directors feature to assist us a little. This then becomes:

%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"

这个.i文件大大简化了java_callback内部所需的代码,现在可以替代以前的libffi和C实现.几乎所有的变化都与明智地领导董事和修复一些C主义有关.现在我们要做的就是从回调中调用纯虚拟C ++方法,SWIG生成了处理其余代码的代码.

This .i file, which has vastly simplified the code required inside of java_callback is now a drop-in replacement for the previous libffi and C implementation. Pretty much all the changes are related to enabling directors sensibly and fixing a few C-isms. All we need to do now is call the pure virtual C++ method from within our callback and SWIG has generated code that handles the rest.

这篇关于如何使用SWIG在Java中包装回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆