« iTunes Transport Protocol for Internet Radio | Main | Electronics Stores in the Bay Area »

Java Native Interface (JNI) and Experiments in Pain

One of my recent objectives has been to integrate logging messages from a C library invoked using JNI with the logging services in the Java application. So, the path of execution is: Java to C, then C to Java. The intense intermingling of C and Java scared me at first, which was perfectly appropriate as a I would come to learn.

Providing a means of invoking C library function from Java is pretty easy. Simply define native methods in your Java class, compile the C header from the compiled Java byte-code, and develop your C functions to match those in the header. The C functions include a JNI Environment (type JNIEnv*) parameter which provides a reference to the Java runtime environment within the scope of the calling thread. The JNI Environment variable is the hub of activity in JNI world, so it's tempting to want to keep a global reference to it. But this is something you should never do, especially when it's possible that your C library will be accessed by more than one thread. I learned this the hard way...

Here's an example drawn from my work that illustrates how to make this work. When the C library is loaded by the Java application, I invoke a native method (C function) called init which sets a global variable to reference the Java Virtual Machine. This reference can be used to get a handle to the environment associated with the current thread. Very useful. Here's what the init function looks like:

// Here's the global variable for the JavaVM
JavaVM* jvm = NULL;

// global variable for the logger instance - MUST - be released in destructor to avoid 
// memory leaks
jobject logger = NULL;

JNIEXPORT void JNICALL Java_com_foo_Bar_init
  (JNIEnv *env, jobject obj)
{
	jclass loggerClass = (*env)->FindClass(env, "com/foo/LoggerFactory");
	jmethodID loggerConstr = (*env)->GetStaticMethodID(env, loggerClass, "getInstance", 
            "()Lcom/foo/ILogger;");
	logger = (*env)->CallStaticObjectMethod(env, loggerClass, loggerConstr);
	logger = (*env)->NewGlobalRef(env, logger);

	(*env)->GetJavaVM(env, &jvm);	
}

// Resonsible for deleting references to Java objects made by global variables.
void EXPORT_LIB_DESTRUCTOR destroy()
{
	JNIEnv *env = NULL;
	(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2);
	
	if (env && logger)
	{
		(*env)->DeleteGlobalRef(env, logger);
	}		
}

I have C function called getEnvironmentVariable, which has a handle to the JNI Environment. The JNI Environment reference is not in scope when the function lookupVar is called:

JNIEXPORT jstring JNICALL Java_com_foo_Bar_getEnvironmentVariable
  (JNIEnv *env, jobject obj, jstring var)
{
	const char *varName = (*env)->GetStringUTFChars(env, var, NULL);
	if (varName == NULL) {
		return NULL;
	}
	const char *envVal = lookupVar(varName);
	if (envVal == NULL) {
		return NULL;
	}
	jstring retval = (*env)->NewStringUTF(env, envVal);
	(*env)->ReleaseStringUTFChars(env, var, varName);
	return retval;
}

The lookupVar function may need to access logging facilities in Java. I've provided a log function which uses the jvm global variable to get a reference to the JNI Environment for the current thread:

void log (char *msg) {
	JNIEnv *env = NULL;
	(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2);
	if (env) {
		jstring stringMsg = (*env)->NewStringUTF(env, msg);
		loggingMethod = (*env)->GetMethodID(env, loggerClass, "debug", 
                   "(Ljava/lang/String;)V");
		(*env)->CallVoidMethod(env, logger, loggingMethod, stringMsg);
		(*env)->DeleteLocalRef(env, stringMsg);
	}				
}

This should provide a fairly complete example of how to use JNI to call back into a Java application from C without having a handle to the JNI Environment information. Here are some good resources I came across along the way: