How to Create Your Own JVM Launcher
Recently I had come across a problem that required me to create a Java launcher. I could not use java.exe/javaw.exe for this problem. I came across the Java Invocation API during my search for a solution and it was exactly what I was looking for. This is to be a tutorial on how to go about writing your own Java launcher using this API.
To accomplish creating my own launcher for Windows, I will be using Microsoft Visual Studio 2008, and the Java JDK 1.6.0_05. I started by creating an empty Win32 project using Visual C++. I created JavaLauncher header and cpp files. I also needed to include jni.h and jni_md.h in my project. These files can be found in the JDK under jdk1.6.0_05\include.
From the jvm.dll file in the JRE, we need to retrieve the method
jint JNI_CreateJavaVM(JavaVM **p_vm, JNIEnv **p_env, void *vm_args);
This is the call that does the actual JVM creation. It returns a jint (negative if there was an error, 0 for success).
In my header file, I have defined a few constants.
#define CLASSPATH "-Djava.class.path=examples-1.0.jar"
#define MAIN_CLASS "com/billgonemad/examples/HelloWorld"
Notice that the MAIN_CLASS uses the '/' character as a package seperator and not the '.' character.
The first thing that has to be done is to get the path to the java directory. I am doing this by getting the environment variable JAVA_HOME. My computer has this set to "C:\Program Files\Java\jdk1.6.0_05". If you try to run the example on your computer, make sure to have this environment variable set to where Java is installed on your system. To get the environment variable, I used the call,
GetEnvironmentVariableA("JAVA_HOME", javaHome, MAX_PATH + 1);
where the first parameter is the name of the variable, the second is a char array, and the last is the length of the array.
After the environment variable has be retrieved, the rest of the path must be appended to the variable. The DLL is located at %JAVA_HOME%\jre\bin\client\jvm.dll. Once this is done, you can load the library with the call LoadLibrary. If this call fails, my HINSTANCE jvmLib will be NULL. A call to GetLastError() will return the appropriate error code.
If the DLL was successfully loaded, I can now load the address for the JNI_CreateJavaVM method. This is accomplished by using the call, GetProcAddress. Once you have loaded the method, we can proceed to start the jvm.
We begin by getting the arguments for the jvm. I declared a JavaVMOption array of size 1. This will be used to hold the classpath for the java program. It is also used for any other jvm options that may be used, but for my example, I am only using the java.class.path option. Also needed, is a JavaVMInitArgs structure. Inside this struct are four variables (ignoreUnrecognized, options, nOptions, and version). I set ignoreUnrecognized to TRUE (this ignores any options that the jvm does not recognize), options is set to my JavaVMOption variable, nOptions is set the length of the options (one for me), and version set to JNI_VERSION_1_6 (the version I am using).
Now onto creating the JVM. We do this by using the call that we retrieved from the jvm.dll file. The call, createJavaVM(&javaVM, (void**)&jniEnv, &initArgs), will return 0 if successful, or a negative error code if there was an error. Once we have successfully created the jvm, all that is left is to call the main method.
First, we need to find the class with our main method in it. This is done by using the call, helloWorldClass = jniEnv->FindClass(MAIN_CLASS), where helloWorldClass is a jclass object. If the call is unsuccessful, the call jniEnv->ExceptionOccurred() will return a jthrowable object with the exception. Also, make sure to check the helloWorldClass for null. Next, you need to get the main method that will be called. jniEnv->GetStaticMethodID(helloWorldClass, "main", "([Ljava/lang/String;)V") will return a jmethodID object. We pass it the class to search for the method, the method name, and the argument parameter types for the method. Just like finding the class, we need to check for exceptions and null. One last thing to find, the String class for our arguments. We do this following the same precedure that we found our HelloWorld class.
We need to create a String array for our arguments to the method. jniEnv->NewObjectArray(0, stringClass, NULL) will create the empty array for us. Now we can call our main by using the call, jniEnv->CallStaticVoidMethod(helloWorldClass, mainMethod, mainArgs). Be sure to check for exceptions on this call, this way you can print off any errors that will occur in the JVM by using jniEnv->ExceptionDescribe().
And that is that! Now you can go out and create your own JVM launcher for your programs.