如何在JNI中实现观察者模式

4

最近我需要用Java包装一个C/C++库,其中有一个方法接受一个函数作为参数。这基本上是观察者(也称为监听器)模式:

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()?

1个回答

8

实施这个解决方案花了我很长时间,需要从互联网上各处收集信息。现在提供给大家:

//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);
    }
}

感谢您发布解决方案!自从您的回答以来已经过了一段时间,您有机会实现TODO项吗?如果是这样,您是否可以更新您的答案呢? - TCSGrad
抱歉,我已不再参与这个项目,并且我没有完成那些待办事项。 - Emmanuel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接