google / jni-bind

JNI Bind is a set of advanced syntactic sugar for writing efficient correct JNI Code in C++17 (and up).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Difficulty expressing a method call

centromere opened this issue · comments

Hello. I am having difficulty expressing a method call. My Kotlin definitions are as follows:

sealed interface Event<T> {
    data class Created<T>(val obj: T) : Event<T>
    data class Updated<T>(val obj: T) : Event<T>
    data class Deleted<T>(val obj: T) : Event<T>
}
interface EventListener<T> {
    fun onEventReceived(event: Event<T>)
}
val callback = object : EventListener<T> {
    override fun onEventReceived(event: Event<T>) {
        trySendBlocking(event)
    }
}

someJNICall(callback)

My C++ code is receiving a jobject eventListenerObj representing the callback defined above, and I would like to invoke its onEventReceived method. So far I've attempted to use the following definitions:

static constexpr jni::Class JavaObject {
    "java/lang/Object",
};

static constexpr jni::Class EventCreated {
    "com/myproject/Event$Created",
    jni::Field { "obj", JavaObject },
};

static constexpr jni::Class EventListener{
    "com/myproject/EventListener",
    jni::Method{"onEventReceived", jni::Return{}, jni::Params{EventCreated}},
};
auto javaEvent = jni::LocalObject<EventCreated> {};
javaEvent["obj"].Set(javaRpcObject);

jni::LocalObject<EventListener> eventListener{eventListenerObj};
eventListener("onEventReceived", javaEvent);

and I am experiencing a runtime error:

java_vm_ext.cc:594] JNI DETECTED ERROR IN APPLICATION: JNI CallVoidMethodV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/myproject/EventListener;.onEventReceived(Lcom/myproject/Event$Created;)V"

Running javap against EventListener.class, I see the following output:

{
  public abstract void onEventReceived(com.myproject.Event<T>);
    descriptor: (Lcom/myproject/Event;)V
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    Signature: #8                           // (Lcom/myproject/Event<TT;>;)V
    RuntimeInvisibleParameterAnnotations:
      parameter 0:
        0: #9()
          org.jetbrains.annotations.NotNull
}
Signature: #3                           // <T:Ljava/lang/Object;>Ljava/lang/Object;

What is the proper way to call onEventReceived?

EDIT:

I was able to invoke the method successfully in the following manner:

jclass c = env->GetObjectClass(eventListenerObj);
jmethodID m = env->GetMethodID(c, "onEventReceived", "(Lcom/myproject/Event;)V");
env->CallVoidMethod(eventListenerObj, m, javaEvent.operator _jobject *());

How that translates to this library is still an open question.

Hello, thanks for your interest in JNI Bind and the detailed question.

I think you possibly are missing the "type erasure" of your interface. Created is actually called something like com/myproject/Event$Created<Ljava/lang/Object;>

So too I think this applies to EventListener as it is generic. It might be worthwhile to just print the instance of the class object using toString and maybe it will give you the proper signature, although, I've used classes with type erasure before successfully, and I believe that's your mistake.

Please let me know if it works.

Thank you @jwhpryor.

The following code:

callback::class.java.getDeclaredMethods().forEach { method ->
    println(method.toString())
}

leads to the following output:

public void com.myproject.ClusterSession$watch$1$callback$1.onEventReceived(com.myproject.Event)

It appears as though I need to somehow construct an Event, but Event is an interface, not a class. This is my attempt to do so in C++:

static constexpr jni::Class Event{
        "com/myproject/Event",
        jni::Constructor{EventCreated},
};

static constexpr jni::Class EventListener{
        "com/myproject/EventListener",
        jni::Method{"onEventReceived", jni::Return{}, jni::Params{Event}},
};
auto javaEvent = jni::LocalObject<EventCreated> {};
javaEvent["obj"].Set(javaRpcObject);

jni::LocalObject<Event> someEvent{javaEvent};
jni::LocalObject<EventListener> eventListener{eventListenerObj};
eventListener("onEventReceived", someEvent);

The code above type checks, but it fails at runtime:

java_vm_ext.cc:594] JNI DETECTED ERROR IN APPLICATION: JNI NewObjectV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/myproject/Event;.<init>(Lcom/myproject/Event$Created;)V"

It makes sense to me why it fails, because Event is not a class, and therefore it does not have any constructors.

I agree, it would make sense that you cannot construct an instance of an interface, and when you write jni::LocalObject<Event> someEvent{javaEvent};, you are essentially attempting to call a constructor (that doesn't exist) with an object.

Instead, you probably want to write jni::LocalObject<Event> someEvent{CreateCopy{}, javaEvent}; or jni::LocalObject<Event> someEvent{AdoptLocal{}, javaEvent}; or even I think just jni::LocalObject<Event> someEvent{std::move(javaEvent)}; I think would work (it depends on which of these you want).

https://github.com/google/jni-bind?tab=readme-ov-file#local-and-global-objects might be useful context for you.

Just checking in, did you manage to solve your issue? If I don't hear back I'll close this issue out, but I wanted to give you a chance to respond.

Feel free to reopen as needed. Best of luck!

Hi @jwhpryor. Apologies for the delay. It seems that jni::LocalObject<Event> someEvent{std::move(javaEvent)}; is exactly what was needed, thank you.