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!