単純な解決策は、グローバル変数を使用してjobject
を格納するだけでコールバック中に渡された引数の中に格納することができないという前の質問に私の答えを適応させることです。 (ライブラリの作成者側では、コールバックが発生したときに関数に渡されるコールバックを設定する際に、引数を渡す方法がないように思われません。通常はvoid*
または単にthis
。それは私)
を私は個人的にstd::function
を使用しているだろうし、我々は、単にここSWIGディレクターに頼ることができるが、それは、このシナリオではオプションではないようライブラリーを設計していた場合、それはC++だと考えますこれを介して動作するために、私は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の練習問題として扱っています。グローバル変数とstruct
保持コールバック情報の交換を除いて、earlier answerに1-1をマッピングし、一般に
%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"
:あなたは、その後のようなSWIG用のインタフェースファイルを書き込むことができる場所にそれと
および
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
への複数の呼び出しは、私たちが期待するような振る舞いをしません)私の好みの解決方法は、libffiを使ってクロージャを生成することです。基本的には、アクティブなコールバックごとに新しい関数ポインタを作成して、どのJavaオブジェクトが呼び出されているかの知識を、コールバックが発生するたびに暗黙的に渡すことができるようにします。 。。達成した
%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 has an example of closuresかなり密接に私たちのシナリオに合う
テストケースが変更されていませんこれを説明するために、SWIGインタフェースファイルは、このになってきましたlibffiを使用してクロージャを作成してグローバルを削除するという私たちの目的Callback
は、CとJavaコンポーネントを組み合わせた抽象クラスになりました。実際には抽象メソッドLog
の実装を保持し、ライフサイクルを管理することですこれを実装するために保持する必要のある残りのCデータのうち、ほとんどのlibffiの作業は、%extend
SWIG内で行われますこれは、closureのlibffiのドキュメントをほぼ反映しています。 java_callback
関数は、グローバルルックアップの代わりに必要なすべての情報を格納するために渡されたuserdefined引数を使用し、ffi呼び出しによって関数引数を受け渡す必要があります。私たちのCallback_t
タイプマップでは、私たちが本当に必要とするクロージャへの関数ポインタの設定を助けるために、%extend
で追加した特別な関数を使用するようになりました。
ここで注目すべき重要な点は、Callbackインスタンスのライフサイクルを管理するJava側の責任があることです.C側からその情報を表示する方法がないため、早すぎるガベージコレクションが危険です。
これをコンパイルして実行するには、implements
がrun.javaでextends
になる必要があり、コンパイラには-lffi
が追加されている必要があります。それ以外は以前と同じように動作します。インスタンスの言語が包まれているので
は、C++、我々は実際にSWIGの取締役に頼ることで少しは私たちに少しを支援する機能JNIコードの一部を簡略化することができませんCです。次いで、これを次のようになる。
%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"
大幅java_callback
の内部に必要なコードを簡略化しています。この.iファイルは、今や以前libffi及びCの実装のためのドロップイン置換です。ほとんどすべての変更は、取締役を分かりやすくし、いくつかのC面を修正することに関連しています。私たちが今必要とするのは、コールバック内から純粋な仮想C++メソッドを呼び出すことだけです。SWIGは残りの部分を処理するコードを生成しています。
あなたのコールバックのuserdata引数が実際には 'Log' typedefのようです。あまりにもそれを私たちに示すことはできますか?おそらくボイド*が内部にあるか、そうでなければあなたにボイドを与えるためにマッサージすることができます*。おそらくそれは 'char *'のロギングに関するものなので、実際には文字列でなければならず、ロギングが必要なメッセージですか? – Flexo
'Log'は実際にはenumですが、私の悪いことは残念です。 'enum Log:uint32_t { blah = 1、 blahblah = 2、 blahblahblah = 3 }'はい、 'char *'はnullで終了します。 –
右ですが、 'char *'文字列の内容を制御しません。つまり、アプリケーションがコールバックに情報を渡す方法です。あなたはそれを変更できますか?そのような場合には、あまり設計されていないインターフェースですが、いくつかの回避策があります。 – Flexo