2016-10-26 9 views
0

とJavaでコールバックをラップする方法: 私は私の質問は似ていますが、私のコールバックを取りながら、その質問への答えは、void*ユーザデータ引数に固有の仕立てたことを実現しHow should I write the .i file to wrap callbacks in Java or C#このスレッドからフォローアップSWIG

enumとchar*です。 Logが列挙型である

これは私のヘッダファイルに私のコールバックが定義され、使用される方法です

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

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

私はJNIとSWIGにとって新しかったので、これを包む方法に関する具体的なガイドが必要です。上記のスレッドに示されたものと同様です。

ありがとうございます。

+0

あなたのコールバックのuserdata引数が実際には 'Log' typedefのようです。あまりにもそれを私たちに示すことはできますか?おそらくボイド*が内部にあるか、そうでなければあなたにボイドを与えるためにマッサージすることができます*。おそらくそれは 'char *'のロギングに関するものなので、実際には文字列でなければならず、ロギングが必要なメッセージですか? – Flexo

+0

'Log'は実際にはenumですが、私の悪いことは残念です。 'enum Log:uint32_t { blah = 1、 blahblah = 2、 blahblahblah = 3 }'はい、 'char *'はnullで終了します。 –

+0

右ですが、 'char *'文字列の内容を制御しません。つまり、アプリケーションがコールバックに情報を渡す方法です。あなたはそれを変更できますか?そのような場合には、あまり設計されていないインターフェースですが、いくつかの回避策があります。 – Flexo

答えて

0

単純な解決策は、グローバル変数を使用して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は残りの部分を処理するコードを生成しています。

+0

ありがとう、本当にありがとうございます。最後の.i実装を使用します。 –

+0

@arminsh FYI最新の実装を使用して、最初に – Flexo

+0

と書いたので、最後の.i実装を整理しました。私は99%の割合で '0x00007FFF404AC3C3(****。dll) java.exeで:0xC0000005:アクセス違反が0xFFFFFFFFFFFFFFFFの場所を読み取っています。あなたが私に警告した時期尚早のガベージコレクションについては推測していますか? 'run r = new run();'のライフサイクルをどのように管理すればよいですか?私は 'r'を' final'と 'r.swigReleaseOwnership();'を無駄に宣言しようとしました。ときどきランダムな間隔で 'Log'関数が動作し、メッセージを出力することができます。 –

関連する問題