Reputation: 783
This is way over my head so here we go.
I am setting a function pointer in C++ with an interface Java object which looks like that on the Java side:
Info_t info_t = new Info_t();
info_t.setCallbackTest(new CallbackTest() {
@Override
public void onCallback(int num) {
System.out.println("callback: " + num);
}
});
where CallbackTest()
is the interface with just one method:
public interface CallbackTest {
void onCallback(int num);
}
and setCallbackTest()
refers to the Java native method which is reflected in the C++ side like that:
JNIEnv *gbl_env;
jmethodID gbl_method;
jobject gbl_callback;
void WrapperFunction(int a) {
gbl_env->ExceptionClear();
gbl_env->CallVoidMethod(gbl_callback, gbl_method, a);
if(gbl_env->ExceptionOccurred()) {
gbl_env->ExceptionClear();
}
return;
}
JNIEXPORT void JNICALL Java_apiJNI_Info_1t_1callbackTest_1set(JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg1_, jobject jarg2) {
Info *arg1 = (Info *) 0;
(void) jenv;
(void) jcls;
(void) jarg1_;
arg1 = *(Info **)&jarg1;
{
jclass clazz;
jmethodID mid;
clazz = jenv->GetObjectClass(jarg2);
mid = jenv->GetMethodID(clazz, "onCallback", "(I)V");
if(mid == 0) {
std::cout << "could not get method id" << std::endl;
return;
}
gbl_env = jenv;
gbl_method = mid;
gbl_callback = jarg2;
}
// here I am setting function pointer to the helper method which invokes the java code
if(arg1) (arg1)->completionCB = WrapperFunction;
}
All of this was written basing on the Implement callback function in JNI using Interface
I have tried calling the onCallback()
method defined in Java on the C++ side and it works (just like in the link above) but now I am trying to set the value on such struct
under C++
typedef void (*callbackFunction)(int);
typedef struct Info
{
callbackFunction completionCB;
void *caller;
} Info_t;
so later on at some point I would like to get the callbackFunction completionCB
back to Java as an object and probably do various stuff with it.
My question is how can I return/map the function pointer behavior from C++ back to the Java object? Basically, I would like to do the same but in the reverse order. I am able to map Java interface onto function pointer now I would like to map function pointer behavior onto Java object.
EDIT: So far I have come up with such getter for it but I am not sure how to proceed
JNIEXPORT jobject JNICALL Java_apiJNI_Info_1t_1callbackTest_1get(JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg1_) {
jobject jresult = 0;
Info *arg1 = (Info *) 0;
callbackFunction result;
(void)jenv;
(void)jcls;
(void)jarg1_;
arg1 = *(Info **)&jarg1;
result = ((arg1)->completionCB);
{
jclass clazz = jenv->FindClass("com/CallbackTest");
???
}
???
return ;
}
Upvotes: 1
Views: 1643
Reputation: 1
You can not cache the JNIEnv value.
Per Chapter 5: The Invocation API of the JNI specification (bolding mine):
Attaching to the VM
The JNI interface pointer (JNIEnv) is valid only in the current thread. Should another thread need to access the Java VM, it must first call
AttachCurrentThread()
to attach itself to the VM and obtain a JNI interface pointer. Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method. The native thread remains attached to the VM until it callsDetachCurrentThread()
to detach itself.
For method id and your callback object, you need to create a global references:
gbl_method = jenv->NewGlobalReference( mid );
gbl_callback = jenv->NewGlobalReverend( jarg2 );
That does create one serious problem, though - that object reference held in gbl_callback
will result in the Java object never getting garbage collected.
To return a C++ callback function, you'd first need to convert the function pointer to a valid Java type. Strict conformance with the C standard (JNI is really C and not C++, so passing objects back and forth gets a bit murky when you mix in C++) means you can't treat the function pointer as anything other than a series of bytes, which means you'd need to convert the function pointer to a Java byte array and return that to Java. Then convert that Java byte array back to a function pointer when you want to use it in native code. That's a lot of code, and in JNI the more code you write the more likely you'll break something - JNI is fragile.
But on both Windows and POSIX, a function pointer will fit into a jlong
, and a jlong
can accurately represent a function pointer's value, so that's a hack that works (and under POSIX it's probably not even a hack, but I'm not going to try finding the actual POSIX function pointer specifications that would allow that cast).
The easiest way to do that is to create a long
field in your Java object, and return the function pointer cast to a jlong
:
JNIEXPORT jlong JNICALL Java_apiJNI_Info_1t_1callbackTest_1set(
JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg1_, jobject jarg2)
{
...
return( ( jlong ) WrapperFunction );
}
That would be called something like this:
// Info_t has a long field called callback
Info_t info_t = new Info_t();
info_t.callback = info_t.setCallbackTest(new CallbackTest() {
@Override
public void onCallback(int num) {
System.out.println("callback: " + num);
}
});
In my experience, the less JNI calls you make, the better. So if you can pass a value via a parameter or return it from a function call, that's simpler, safer, and a lot more reliable than trying to get/set object field values with JNI calls.
But then you'd need to pass the callback value as another parameter:
// function pointer, returns void, takes int arg
typedef void (*funcPtr)( int );
JNIEXPORT jobject JNICALL Java_apiJNI_Info_1t_1callbackTest_1get(
JNIEnv *jenv, jclass jcls, jlong callback, jlong jarg1, jobject jarg1_)
{
funcPtr f = ( funcPtr ) callback;
f( 4 );
...
}
Upvotes: 2