Opinions expressed are solely my own and do not express the views or opinions of my employer.

Sunday, October 28, 2012

Start a Java Virtual Machine on OS X via Java's Invocation API (and using a Java GUI)

Have you ever wanted to start a Java Virtual Machine (JVM) using C/C++ on OS X and were not able to? No? Maybe you wanted to start a Java GUI class using C/C++? Yes? Then we're in the same boat ;)

Starting a JVM using C/C++ is really easy. You can just rely on Java's Invocation API, which is part of the Java Native Interface (JNI), and allows you to create and manage JVMs in C/C++ and of course, connect to programs developed in Java.
The only thing you have to do is, include the jni-headers link against the JNI C-library and use the following code. The comments should explain what happens.


#include <jni.h>

/* Some initialization stuff */
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
int res;
JavaVMOption* options = new JavaVMOption[1];
/* Add the path to the java classes you want to use in C here */
options[0].optionString = "-Djava.class.path=/some/java/class/path/";
vm_args.version = JNI_VERSION_1_6;
vm_args.options = options;
vm_args.nOptions = 1;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM; don't forget to check the return value! */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (res < 0) exit(1);
/* here goes your JNI code */
/* Don't forget to destroy the JVM at the end */
jvm->DestroyJavaVM();

Now that was easy! Let's have a look on how to create a Java object and call functions (or in our case, start the Java static main-method).


jclass main_class;
jmethodID main_method_id;
/* At first find the Java class. The package uses the slash- instead of the dot-notation! */
main_class = (*env)->FindClass(env, "com/example/TestGUI");
if (main_class == NULL) {
jvm->DestroyJavaVM();
exit(1);
}
/* Now let's get a static method ID, using the class, the name of the method and the parameter types */
main_method_id = (*env)->GetStaticMethodID(env, main_class, "main", "([Ljava/lang/String;)V");
if (main_method_id == NULL) {

jvm->DestroyJavaVM();
exit(1);

}
/* Finally, we can call the method */
(*env)->CallStaticVoidMethod(env, main_class, main_method_id);


Again, not really hard. A quick clarification: this is C-code. If you use C++, calls to env methods don't need the first reference to env!
What could confuse you is the weird way we specified the Java method's parameter types.

JNI uses a very interesting notation for parameter types. The options inside the parenthesis refer to parameters. The option after the closing parenthesis the return type. In our case, we call a method (static void main(String[])) with the void return type, which is denoted by the V. The String type is denoted by an L, the fully qualified class name ending with a semicolon ;. The L just tells JNI that a fully qualified class follows. And because we're using an array of Strings, a square bracket [ is put in front of the type.
You can find the full specification here. It's not that hard to remember but you'll have to get used to this notation.

Great, now we created a JVM and ran the Java main method. So what was the problem again? Unfortunately OS X.
Remember our initial problem? Running a GUI using this approach. Try it and you will find our that the problem you will run into is an error telling you that it's not possible to run AWT (Swing builds on top of AWT too ;) in the startup thread.
A possible error message could be something like "Can't start the AWT because Java was started on the first thread.  Make sure StartOnFirstThread is not specified in your application's Info.plist or on the command line".
The solution? Just use OS X's version of POSIX to create a thread. (Apple actually fully implemented the pthread API, not like the semaphore API ;)
Simple as that? No, not really!
If you start the JVM in a separate thread (or at least call the Java main-method in a separate thread), you will never see anything, unless you wait for the pthread to join. (I actually never tried waiting for the JVM thread using pthread_join; so maybe it won't even work) It should also be possible to start the GUI in a Java thread, but why would you do that if you can use C :P

So instead of waiting for the thread, you could or maybe even should just link your program to OS X's CoreFoundation framework (add -framework CoreFoundation to the C linker). You also have to change your implementation to #include <CoreFoundation.h> and after creating the JVM thread, call CFRunLoopRun() which will start the CoreFouncation Run (Event) Loop.

Now everything should work as expected and you can all some more Java functions or implement a C native interface and call C functions from Java ;)
But more on this in another post. I hope you enjoyed this technical post and it helped you solving you problem or will help you with one of your future projects! As usual I'd be happy to hear from you in the comments.