2013-11-28 34 views
3

我最近不得不用Java包裝一個C/C++庫。其中一種方法接受函數作爲參數。這基本上是觀察者(又名監聽)模式:如何在JNI中實現觀察者模式

void setLogFunction(const std::function<void(std::string&, std::string&)> &callback) 
{ 
    _callback = callback; 
} 

在Java端,你不能傳遞功能,但你可以傳遞一個對象與log()方法。

interface Observer { 
    public void log(String prefix, String message); 
} 

class MyLibrary { 
    public MyLibrary() { 
    initCpp(); 
    } 
    public native void initCpp(); 
    ... 
    public native void setObserver(Observer observer); 
} 

如何在JNI中實現setObserver()?

回答

6

它花了我永遠的實施這個解決方案。我不得不從互聯網上獲取信息。這裏是:

//Optional, this is just so that I can retrieve the C++ MyLibrary instance 
//easily from the Java. 
JNIEXPORT void JNICALL Java_MyLibrary_initCpp(JNIEnv* env, jobject javaMyLibrary) { 
    //Save the C++ version of MyLibrary as a field inside the Java MyLibrary. 
    MyLibrary * lib = new MyLibrary(); 
    env->SetLongField(javaMyLibrary, CPP_MYLIBRARY_POINTER_FIELD, ptr_to_jlong(lib)); 
} 

JNIEXPORT void JNICALL Java_MyLibrary_setObserver(JNIEnv* env, 
     jobject javaMyLibrary, jobject javaObserver) { 
    //Retrieve the CPP version of MyLibrary. For me, I stored it inside the java 
    //object as a field, but you can implement it any way you want. 
    MyLibrary* cppMyLibrary = (MyLibrary*)jlong_to_ptr(
     env->GetLongField(javaMyLibrary, CPP_MYLIBRARY_POINTER_FIELD)); 
    if (cppMyLibrary == NULL) { 
     //Handle the problem 
     return; 
    } 
    jthrowable exc = NULL; 

    //Keep the jvm object around; the env is not available in another thread 
    //but can be obtained from the jvm. 
    JavaVM* jvm; 
    env->GetJavaVM(&jvm); 

    //The observer has to be made global; it's not available in another thread. 
    jobject observer = env->NewGlobalRef(javaObserver); 
    //TODO: retrieve the previous observer and clean it up with DeleteGlobalRef() 
    //TODO: clean up this observer when destroying MyLibrary with DeleteGlobalRef() 

    try { 
     //At this point, both "jvm" and "observer" are accessible from the other thread. 
     cppMyLibrary->setLogFunction([jvm, observer] (std::string& p0, std::string& p1) { 
      JNIEnv* env; 
      jvm->AttachCurrentThread(&env, NULL); //Obtain the env 
      jclass clazz = env->GetObjectClass(observer); 
      jmethodID meth = env->GetMethodID(clazz, "log", 
        "(Ljava/lang/String;Ljava/lang/String;)V"); 
      jstring s0 = env->NewStringUTF(p0.c_str()); 
      jstring s1 = env->NewStringUTF(p1.c_str()); 
      env->CallVoidMethod(observer, meth, s0, s1); 

      //TODO: make sure this is called even if there's an exception! 
      jvm->DetachCurrentThread(); 
     }); 
    } catch (...) { 
     exc = //handle your exceptions here 
    } 

    if (exc != NULL) { 
     env->Throw(exc); 
    } 
}