Java SE 6
Creating a Custom Launcher
Creating a Custom Launcher
By: John Chamberlain
Sep. 1, 2002 12:00 AM
The most frustrating and error-prone aspect of Java for the average user is starting a Java program. The monumental confusion of batch files, scripts, and command-line cut-and-paste that's necessary to start a Java program using the default launcher is an ongoing problem area even for veteran developers.
This article shows how you can wipe away the whole mess and easily write custom launchers for your applications. A custom launcher makes startup as simple as point-and-click and can be the difference between a program appearing professional or appearing unusable. The specifics on making launchers for all the major platforms are also covered.
A long-time barrier to user acceptance of Java has been the confusing and unfriendly default launcher. It requires long strings of command-line arguments before it will start a Java program. Both users and software developers constantly receive the java.lang.NoClassDefFound exception and other errors. Sun's noble response to this problem is to provide a public API, the invocation interface, that can be used to start the JVM and accompanying Java program. Although a few commercial applications use this API, it's woefully underutilized overall. I'll show how easy it is to create a custom launcher and provide templates that you can use to automate startup of your Java programs. There's even a generic configurable launcher that's ready to run, no compilation necessary.
I focus on the three major desktop platforms: Windows, Mac OS, and Unix. Each platform has its own quirks, but using a custom launcher brings benefits that are common to all three, such as:
How Java Programs Are Launched
As the figure suggests, any native executable can start a Java program. A Java launcher is a native executable dedicated solely to starting Java programs. The most commonly used launchers are the ones Sun supplies in the /bin directory of the Java runtime distribution. In the case of the Windows platform, these programs are "java.exe" and "javaw.exe". The former opens two windows: a console that receives System.out/err and output from the launcher and the Java window itself. The latter, a windowless launcher, opens only the Java window. On J2SE/EE platforms the virtual machine is implemented as a dynamic link library that's also in the /bin directory. On Windows it's called "java.dll", on Unix "java.so". Loading the VM equates to loading this DLL.
Users specify options to the VM in two ways. They can put the options on the command line to the launcher and/or define environment variables with the desired settings. One of the options, the startup class, can only be specified on the command line. This bifurcation of the execution configuration is a common source of confusion that can be eliminated by using a custom launcher.
When the virtual machine has finished running the main() method of the startup class, the launcher calls destroy() on the VM to free any detachable resources and then exits. Note that there's no way to unload a VM once it's been loaded. This makes no difference to a launcher since it will exit as soon as the Java program is done; however, for a native application that embeds a VM, such as a browser, it means there's a permanent commitment of memory that can't be reclaimed.
Nuances of Creating a Windows Launcher
First, use a WinMain() entry point as you would for most Windows applications. Also, you need to prototype CreateJavaVM() to use the stdcall calling convention by typedef'ing it as a pointer to CALLBACK. These are Windows-specific requirements. Another platform-specific nuance is loading the VM DLL. The most reliable way to load the VM is by an explicit call to LoadLibrary:
HINSTANCE hJVM = LoadLibrary(sJVMpath.c_str());
First determine the path of the JVM's DLL and then explicitly load it. This differs from the example in The Java Native Interface, which uses implicit loading. The problem with implicit loading is that it makes assumptions about the location of the DLL that might not be true for all environments. By explicitly loading the JVM you can place it anywhere you like in your distribution and verify that it's really there before attempting to load it. Once you load the JVM, obtain a function pointer to CreateJavaVM() by using the kernel call GetProcAddress() and then calling that pointer to start the VM.
The next nuance in the listing is that the separators used in the startup class identifier are slashes, not dots. So in the listing the startup class is "javabunny/JavaBunny", not "javabunny.JavaBunny". This is because FindClass() is a virtual machine call and the virtual machine internally uses the slash as its package separator. By the way, the example hard codes the startup class (and other values). This may be appropriate for a shrink-wrapped product release, but in a development environment you'll probably want to pull this value from a configuration file. Later, I'll describe a more generic template that does this.
The example determines the startup method ID by using the JNI call GetStaticMethodID(). This call requires the method name ("main") and the type descriptor "([Ljava/lang/String;)V". This type descriptor means the method takes an array of strings as an argument and has a return type of void. For more information on type descriptors see The Java Virtual Machine Specification (see Resources). Notice that when you create a custom launcher you're not restricted to using a static void method called "main". You can start with any method at all, even an instance method or constructor.
The last tricky point of a launcher is hidden behind the following line at the end of the listing:
This statement looks like optional cleanup added as an afterthought to program execution. Not true! If the Java program is multithreaded, it will still be executing during this call. For example, if a Swing program runs and its main method exits, this line will execute and block until all nondaemon threads have completed. This blocking behavior makes it critical that you include this line. If you omit it, the program will exit as soon as the main thread terminates, even if other threads (like the event loop of your GUI) are still running.
Resource paths can be made flexible enough that they don't need to be configured, but some values will need to be configurable outside of the launcher, especially in an oft-changing development environment. These include:
If these restrictions don't faze you, create a native launcher on Classic Mac by using Apple's old native toolkit called JDirect (don't confuse this with the obsolete "J/Direct" that worked with Microsoft's J++). A much easier way to create a clickable icon, however, is to use a special Apple tool called "JBindery". This tool creates a distribution that so closely resembles a native application that writing a native launcher is unnecessary. You can completely configure your distribution package using JBindery, including defining security settings and the appearance of the Java window. When you're done, use ResEdit to add a custom icon to the package and it's ready to run. Apple considers Mac Classic, JBindery, and this whole methodology obsolete, but if you want to support the many users who are sticking with OS 9.1/2, it's your best option.
The new Mac world is all OS X. In OS X the application layer is called "Cocoa" and you access it with Objective-C. Is that retro or what? Despite how weird it sounds, Java support is excellent because an interface called the "Java Bridge" wraps the Java Native Interface (including the invocation interface) and makes a seamless connection between your native code and Java code.
As with the Classic OS, writing a native launcher is unnecessary, since Apple has provided a great bundling tool, MRJAppBuilder. If your Objective-C skills are a little rusty and you're working solely in Java, the best approach is to use MRJAppBuilder. Apple has designed this bundler especially for packaging Java applications. Note that the bundling framework the tool uses is the standard way to deploy all Cocoa applications, not just Java applications. This enlightened approach to application distribution means that on OS X, a bundled Java application is externally indistinguishable from an Objective-C application and behaves in all ways like a native executable.
The powerful capabilities of the bundlers (JBindery for Mac Classic and MRJAppBuilder for OS X) eliminate the need for a custom launcher on the Macintosh unless you're doing something offbeat such as starting from an instance method. If you really need to go native on the Macintosh, the article's download package has some code examples that will get you started. Otherwise, stick with the bundlers and you can sit back and laugh at the PC programmers while they fiddle endlessly with batch files.
Even so, a custom launcher still has many benefits under Unix. For example, in a process listing, the Java command line is usually so long it gets truncated, and on a server machine running multiple VMs it can be a pain to identify which process is which. You can create a custom launcher that simplifies and shortens these startup commands and thus make the process listing more meaningful.
One of the advantages of Unix's simplicity is that its launcher code is the easiest of all the platforms. The basic Unix launcher is the same as the Windows example shown in Listing 1 without the Windows-specific type conversions and Windows configuration issues (see the download package for an example). Another advantage is that a Unix launcher will generally work in any Unix environment as long as it's recompiled once again, something that the installation script manages.
The disadvantage of this simplicity as compared to other OSs is that you are more or less obliged to use scripts of some sort even if you do implement a custom launcher. Good thing Unix has such great scripting capabilities.
Reader Feedback: Page 1 of 1
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
SYS-CON Featured Whitepapers
Most Read This Week