1

I have a c++ function that I want to call from my Kotlin code. That c++ function gets a callback function as an argument, doing some work and calls the callback when completes.

I already done it a few times before and everything was OK. However, I want to wrap it in a way so instead of passing a callback, it will return an Observable that will emit a value when the callback is called.

I created an example with a simpler code. What I did so far:

Kotlin code:

fun someFunc(str: String): Observable<String> {
    val subject = PublishSubject.create<String>()
    nativeFunc(object: TestCallback {
        override fun invoke(event: String) {
            println("Callback invoked. subject = $subject")
            subject.onNext("$event - $str")
        }
    })
    return subject
}

private external fun nativeFunc(callback: TestCallback)

Interface in Kotlin for the callback function:


interface TestCallback {
    fun invoke(event: String)
}

Native JNI code:

extern "C"
JNIEXPORT void JNICALL
Java_com_myProject_TestClass_nativeFunc(JNIEnv *env, jobject thiz, jobject callback) {
    env->GetJavaVM(&g_vm);
    auto g_callback = env->NewGlobalRef(callback);

    std::function<void()> * pCompletion = new std::function<void()>([g_callback]() {
        JNIEnv *newEnv = GetJniEnv();
        jclass callbackClazz = newEnv->FindClass("com/myproject/TestCallback");
        jmethodID invokeMethod = newEnv->GetMethodID(callbackClazz, "invoke", "(Ljava/lang/String;)V");
        string callbackStr = "Callback called";
        newEnv->CallVoidMethod(g_callback, invokeMethod, newEnv->NewStringUTF(callbackStr.c_str()));
        newEnv->DeleteGlobalRef(g_callback);
    });
    pCompletion->operator()(); // <--Similar function is passed to the c++ function. Lets skip that
}

A test function to run it all together

@Test
fun testSubject() {
    val testClass = TestClass()
    val someList = listOf("a", "b", "c")
    var done = false
    Observable.concat(someList.map { testClass.someFunc(it) })
        .take(3)
        .doOnNext { println("got next: $it") }
        .doOnComplete { done = true }
        .subscribe()
    while (!done);
}

The test function runs 3 times the someFunc function (which return an Observable instance, emitting a String on completion) and concat all Observables together.

What I would expect to be printed:

Callback invoked. subject = io.reactivex.subjects.PublishSubject@1f7acc8
got next: Callback called - a
Callback invoked. subject = io.reactivex.subjects.PublishSubject@7c9b161
got next: Callback called - b
Callback invoked. subject = io.reactivex.subjects.PublishSubject@6f24486
got next: Callback called - c

However the actual result is:

Callback invoked. subject = io.reactivex.subjects.PublishSubject@1f7acc8
Callback invoked. subject = io.reactivex.subjects.PublishSubject@7c9b161
Callback invoked. subject = io.reactivex.subjects.PublishSubject@6f24486

It seems like everything work as expected, however, although the line println("Callback invoked. subject = $subject") is printed (with the correct subject addresses), the onNext is not working and not emitting anything for some reason. I checked the same functionality without the native callback stuff and everything works fine.

Any suggestions???

4

1 回答 1

0

所以经过一番研究,我发现:

  1. 当我从 Java 调用 C/C++ 函数时,JNI 不会在后台创建任何新线程。[见这里]。因此,
  2. 代码同步运行,这意味着 - 主题发出一个项目,然后函数返回主题并被订阅。所以订阅后,它错过了发射的项目并丢失了它。
  3. 我说“我在没有本机回调的情况下检查了相同的功能并且一切正常。”是错误的。我可能在那里犯了一个错误,使非本机代码异步,这给了我“准时”返回的主题并按预期打印了日志。

解决方案是将 PublishSubject 更改为 BehaviorSubject 或 ReplaySubject 以缓存发出的项目并在订阅后获取它。另一种解决方案可能是将调用切换到本机函数以在另一个线程中运行,因此在函数运行时,主题已经返回并被订阅。

于 2021-01-24T13:10:30.770 回答