jni - How to wrap callbacks in Java with SWIG -
following thread: how should write .i file wrap callbacks in java or c#
i realize question similar answer question tailored specific void*
user data argument, while callback takes enum , char*
.
this how callback defined , used in header file
typedef void (*callback_t)(const log somelog, const char *text); virtual void setcallback(const callback_t somecallback, const log somelog) = 0;
where log
enum.
i'm rather new jni , swig, need specific guide on how wrap this, similar 1 presented in thread mentioned above.
thanks in advance.
the simple solution adapt answer @ previous question use global variable store jobject
can't store inside argument gets passed during callback. (it's bad design on part of library author there doesn't seem way of passing argument in when setting callback made available function, @ time callback happens. that's either void*
or this
. given it's c++ if me designing library i'd have used std::function
, rely on swig directors here, doesn't seem option in scenario)
to work through 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);
and 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); }
(note i'm working through purely exercise in c since c++ interface you've got work might c here anyway).
with in place can write interface file 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 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 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"
in general maps 1-1 onto earlier answer, exception of replacing struct
holding callback information global variables , improving way jnienv
inside callback.
with our manually written callback.java:
public interface callback { public void log(log log, string str); }
that's enough test case compile , 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 java: " + s); } }
which works:
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 java: hello world
since don't using global variables (multiple calls setcallback
java side not behave way we'd expect) preferred solution (in purely c world) use libffi generate closure us. lets create new function pointer each active callback, knowledge of java object being called can implicit passed around each time callback occurs. (this we're struggling solve. libffi has example of closures fits our scenario closely.
to illustrate test case unchanged, 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"
which has achieved our objective of removing globals creating closure using libffi. callback
has become abstract class, mix of c , java components implementing it. goal of hold implementation of abstract method log
, manage lifecycle of rest of c data needs held implement this. of libffi work done inside %extend
swig directive, pretty mirrors libffi documentation closures. java_callback
function uses userdefined argument gets passed in store of information needs instead of global lookups, , has pass/receive function arguments via ffi call. our callback_t
typemap makes use of function added via %extend
assist in setting function pointer closure need.
one important thing notice here you're responsible on java side managing lifecycle of callback instances, there no way make information visible c side, premature garbage collection risk.
to compile , run work implements
needs become extends
in run.java , compiler needs have -lffi
added. other works before.
since in instance language being wrapped c++ not c can simplify of jni code little relying on swig's directors feature assist little. becomes:
%module(directors="1") test %{ #include "test.h" #include <assert.h> #include <ffi.h> %} %feature("director") callback; // rename makes getting c++ generation right simpler %rename(log) callback::call; // make 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"
this .i file, has vastly simplified code required inside of java_callback
drop-in replacement previous libffi , c implementation. pretty changes related enabling directors sensibly , fixing few c-isms. need call pure virtual c++ method within our callback , swig has generated code handles rest.
Comments
Post a Comment