Improve Java Apps on Windows with a Native Launcher

by
Dan Lewis, Senior Software Engineer
Object Computing, Inc. (OCI)

Introduction

What do IntelliJ Idea, Eclipse, and SmartCVS all have in common? If you said Java, you would be correct. But if you said that they all include a native launcher, you'd also be correct! They all include a native launcher for Windows. Why? The reasons fall into three categories: Integration, Deployment and Performance:

Integration

Two common problems with typical Java applications are avoided by using a native launcher. The first is that annoying command prompt windows pop up temporarily. This is indicative of a batch file that is calling one of the default Java launchers (java.exe or javaw.exe). A native launcher avoids this problem by providing a Windows .exe file that the end user can run. This has an additional benefit of hiding certain details about how your program is launched, such as the CLASSPATH and the "main()" class. The second common problem is that a custom icon is not provided. Again, this is avoided by using a native launcher and including an icon in the .exe.

Deployment

Options for deployment range from fancy automated installers, to executable jar files, to "copy and run". Regardless of the level of sophistication you choose, you'll be faced with another question: Do I use the JVM (Java Virtual Machine) that is already installed on the machine? This question will be decided by size of deployment, version sensitivity of your application, and licensing considerations. The technique described in this article requires that the application include a specific JVM with your application.

Another part of deployment is troubleshooting. When you are looking for the process, you don't have to guess which java.exe or javaw.exe is the process for your app. Your .exe name will appear in the list as the process name.

Performance

This is the weakest of the three arguments, but worth mentioning for those of you that are obsessed with performance (like the author). A native launcher is faster than a batch-file launcher because interpreting batch files is slower than running native C code. The amount of time savings should be negligible, but given the slow startup time of the JVM, shaving a few milliseconds off the application start might be worth it to you.

Performance compared to an executable jar file distribution such as JEdit should also be better because startup parameters are compiled into the launcher vs. embedded in a jar file in the manifest. Again, the performance difference is minor, but it might still be worth it to you to eliminate every extra bit of overhead.

Launcher Source Code

It's a little-known fact that the source code to java.exe and javaw.exe are included in your Sun SDK distribution. Of course, this is not the complete source code to the JVM, but the source code for the launcher. In your SDK installation directory (C:\j2sdk1.4.2_04 on my machine), there is a file named src.zip. Unzip it and you will find several directories, including the launcher directory. I like to unzip it into a directory called src under my JDK home and point my Java IDE to it for debugging purposes. Let's have a look at the files in the launcher subdirectory:

java.c
This is the shared launcher source code in C. It contains common code across platforms.
java.h
This is the C header for the java.c file. It contains method signatures for the functions in java.c.
java_md.c
This is the platform-specific launcher source code in C. It contains code specific to Windows.
java_md.h
This is the C header for the java_md.c file. It contains method signatures for the functions in java_md.c.

The good news is that you do not have to edit any of this code to make your own custom launcher. Furthermore, it's probably a good idea not to. Sun may elect to change the launcher code at any time (and has in the past), so you would be forced to merge your changes and re-test.

The Ant Build Script

I provided this script so that you can easily build your own launcher without spending hours trying to get the launcher to build. The default target is deploy, and will compile MyApp.java (an example Java program I included in the resourcesJuly2004.zip file), jar it up, compile the launcher and copy both into the deploy directory.


<project name="myapp" default="deploy">
  <target name="set-properties">
    <property name="deploy"
    	location="deploy"/>
    <property name="dest"
    	location="dest"/>
    <property name="src"
    	location="src"/>
    <property name="jar.name"
    	value="myapp.jar"/>
    <property name="vctk"
    	value="C:\Program Files\Microsoft Visual C++ Toolkit 2003"/>
    <property name="ms.platform.sdk"
    	value="C:\Program Files\Microsoft SDK"/>
    <property name="java.sdk"
    	value="C:\j2sdk1.4.2_04"/>
    <property name="executable.name"
    	value="myapp.exe"/>
    <property name="link.opts"
    	value="/Fe${executable.name} /MT /link advapi32.lib user32.lib jvm.lib"/>
    <!-- The quotes must be double-escaped -->
    <property name="java.args"
    	value="-DJAVA_ARGS="{ \"com.ociweb.jnb.july2004.myapp.MyMainClass\" }""/>
    <property name="app.classpath"
    	value="-DAPP_CLASSPATH="{ \"\\lib\\tools.jar\", \"\\lib\\${jar.name}\" }""/>
    <property name="jdk.minor.version"
    	value="-DJDK_MINOR_VERSION=\"4\""/>
    <property name="jdk.major.version"
    	value="-DJDK_MAJOR_VERSION=\"1\""/>
    <path id="compile-classpath"/>
  </target>
  <target name="setup" depends="set-properties">
    <mkdir dir="${dest}/classes"/>
    <mkdir dir="${dest}/lib"/>
  </target>
  <target name="compile" depends="setup">
    <javac srcdir="${src}" destdir="${dest}/classes">
      <classpath refid="compile-classpath"/>
    </javac>
  </target>
  <target name="compile-launcher" depends="set-properties">
    <exec executable="${vctk}\bin\cl" dir="${src}\launcher">
      <env key="INCLUDE"
      	value="${vctk}\include;${ms.platform.sdk}\include;${java.sdk}\include;${java.sdk}\include\win32"/>
      <env key="LIB"
      	value="${vctk}\lib;${ms.platform.sdk}\lib;${java.sdk}\lib"/>
      <env key="CL"
      	value="-DWIN32 -DJAVAW ${jdk.major.version} ${jdk.minor.version} ${java.args} ${app.classpath} ${link.opts}"/>
      <arg value="java.c"/>
      <arg value="java_md.c"/>
      <arg value="myapp.res"/>      
    </exec>
  </target>
  <target name="jar" depends="compile">
    <jar destfile="${dest}/lib/${jar.name}" basedir="${dest}/classes"/>
  </target>
  <target name="deploy" depends="jar, compile-launcher">
    <copy file="${src}\launcher\${executable.name}" todir="${deploy}\jre\bin"/>
    <copy file="${dest}\lib\${jar.name}" todir="${deploy}\jre\lib"/>
  </target>
</project>

Configuring the Preprocessor, C Compiler, and Linker with Ant

Please note the compile-launcher target. This is responsible for configuring the preprocessor, the C compiler, and the linker. The preprocessor and C compiler are configured with the CL environment variable, in which preprocessor variables are specified. The preprocessor variables passed in (prefixed with "-D") are as follows:

WIN32
This must be set for the launcher to compile properly. It is a boolean preprocessor variable, and should not have an explicit value.
JAVAW
This causes the launcher to be like javaw. This means that it is a Windows program, not a console program. In other words, it will launch with WinMain() instead of main(). One consequence of this is that you will lose System.out and System.err output, which are artifacts of console-based applications. It is a boolean preprocessor variable, and should not have an explicit value. Omit this variable and you will get a java.exe-style console application.
JDK_MAJOR_VERSION
This must be set or the preprocessor will complain. You should take care to match this to the major version of the JVM you deploy. For JDK 1.4, it should be set to 1.
JDK_MINOR_VERSION
This must be set or the preprocessor will complain. You should take care to match this to the minor version of the JVM you deploy. For JDK 1.4, it should be set to 4.
JAVA_ARGS
This is where the class name to run is specified. For example, if your main() method is located in com.foo.bar.myapp, then that should be the value JAVA_ARGS is set to.
APP_CLASSPATH
This is where the classpath is specified. The application home directory is prepended to each item in this list. This should include \lib\tools.jar (included in the JRE distribution) and any other jar files required.

Building your .exe file.

  1. Unzip the resourcesJuly2004.zip file to a directory (referred to as the "project" directory later) on your system.
  2. Install Apache Ant 1.6.1.
  3. Install the Microsoft Visual C++ Toolkit on your system
  4. Install the Microsoft Platform SDK on your system
  5. Locate the file cvtres.exe on your system. On my system it is located in c:\windows\Microsoft.NET\Framework\v1.1.4322. Add this directory to your path.
  6. Copy the contents of src\launcher (found in the src.zip file located in your JDK home directory) into the project\src\launcher directory.
  7. Copy the jre directory of your JDK installation into the project\deploy directory.
  8. Edit the build.xml file, located in the project directory, and change the following properties:
  9. Use you favorite icon editor, or try the free Java applet at http://imageauthor.com to create an icon. Save the .ico file, and run the "rc" (resource compiler) utility that is included in the Platform SDK on the .rc script that I include in the project\src\launcher directory. The result will be a .res file that is compiled in with your app.
  10. Run the ant script.
  11. Test the launcher by navigating to the myapp.exe file in the project\deploy\jre\bin directory and double-clicking.

Packaging/Deployment

All that is left is to deploy the contents of the project\deploy\jre directory to the target system. Please note that I did not include either the JRE or the launcher source in the resourcesJuly2004.zip file to avoid licensing questions.

About the Visual C++ Toolkit 2003

Recent independent tests published in Dr. Dobbs Journal indicate that Visual C++ ranks highly in terms of performance and the highest in terms of ISO standards compliance. See the reference section for references to those articles. Of course, you can use any of the other free compilers available: GCC, Borland, Open Watcom, or MinGW to name a few.

Summary

Successful Java applications such as IntelliJ Idea, Eclipse and SmartCVS include native launchers. In fact, the desire to have a native launcher for Java applications has spawned a mini-industry of both commercial and open-source tools that generate native launchers, including exe4J (commercial), JEXECreator (commercial), Janel (open-source) and others. You can use one of these tools, or use the Ant script provided here and do this task yourself. However you do it, make the effort to build a native launcher for your Windows Java program. Your application will have a more polished look and leave an impression of quality with the user.

References


Valid XHTML 1.0 Strict [Valid RSS]
RSS
Top