home / blog

JNI asynchronous callbacks

If you’re using JNI, you may wish to callback a java method asynchronously. For example event handling from a win32 message pump.

package com.adamish;

public class Foo {
  public native void register();
  public void callback(int val) {
    // do stuff
  }
}

Step one is to have a “register” method which lets the C++ code obtain a reference to the java code to enable the callback.

For efficiently we cache the references to the VM, the object, and the method. This means less work per callback.

For the object reference you must convert the local reference to a global reference. Local references only exist for the lifetime of the JNI bound call. Afterwards they cannot be used, and worse than that attempts to use them fail silently – the java code is simply not called.

// cached refs for later callbacks
JavaVM * g_vm;
jobject g_obj;
jmethodID g_mid;

JNIEXPORT jboolean JNICALL Java_com_adamish_Foo_register
	(JNIEnv * env, jobject obj, jlong hwnd) {
                bool returnValue = true;
		// convert local to global reference 
                // (local will die after this method call)
		g_obj = env->NewGlobalRef(obj);

		// save refs for callback
		jclass g_clazz = env->GetObjectClass(g_obj);
		if (g_clazz == NULL) {
			std::cout << "Failed to find class" << std::endl;
		}

		g_mid = env->GetMethodID(g_clazz, "callback", "(I)V");
		if (g_mid == NULL) {
			std::cout << "Unable to get method ref" << std::endl;
		}

		return (jboolean)returnValue;
}

Now, the actual callback using our cached references from the register() method. There’s quite a bit going on here. Since the callback is on another thread the VM context must be attached to the current thread.


void callback(int val) {
	JNIEnv * g_env;
	// double check it's all ok
	int getEnvStat = g_vm->GetEnv((void **)&g_env, JNI_VERSION_1_6);
	if (getEnvStat == JNI_EDETACHED) {
		std::cout << "GetEnv: not attached" << std::endl;
		if (g_vm->AttachCurrentThread((void **) &g_env, NULL) != 0) {
			std::cout << "Failed to attach" << std::endl;
		}
	} else if (getEnvStat == JNI_OK) {
		//
	} else if (getEnvStat == JNI_EVERSION) {
		std::cout << "GetEnv: version not supported" << std::endl;
	}

	g_env->CallVoidMethod(g_obj, g_mid, val);

	if (g_env->ExceptionCheck()) {
		g_env->ExceptionDescribe();
	}

	g_vm->DetachCurrentThread();
}
This entry was posted in geek and tagged , . Bookmark the permalink.

4 Responses to JNI asynchronous callbacks

  1. JP says:

    Interesting, maybe this is what I’m looking for. I have event handlers on the c++ side, which once triggered needs to make a callback to the java side. I’ve had nothing but JVM crashes when I simply try to use the callback to java call using the cached env and obj in the c++ event handler that was never invoked from the java side.

  2. JP says:

    Am I missing it, where are you setting *g_vm?

  3. Ninfomane says:

    @JP:
    Call the method JNIEnv->GetJavaVM(&g_vm) to retrieve the current VM.

  4. Soana says:

    The much better question is: Where does the “jlong hwnd” in the JNI declaration of the register method come from?
    In the Java part register is declared with no arguments, so no arguments (apart from the JNIEnv and the this-object of course) should appear in the JNI declaation, shouldn’t it?

Leave a Reply

Your email address will not be published.


seven + = 11

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>