Improve Java Apps on Windows with a Native Launcher
By Dan Lewis, OCI Senior Software Engineer
July 2004
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 thejava.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 thejava_md.c
file. It contains method signatures for the functions injava_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>
- <property name="dest"
- location="dest"></property>
- <property name="src"
- location="src"></property>
- <property name="jar.name"
- value="myapp.jar"></property>
- <property name="vctk"
- value="C:\Program Files\Microsoft Visual C++ Toolkit 2003"></property>
- <property name="ms.platform.sdk"
- value="C:\Program Files\Microsoft SDK"></property>
- <property name="java.sdk"
- value="C:\j2sdk1.4.2_04"></property>
- <property name="executable.name"
- value="myapp.exe"></property>
- <property name="link.opts"
- value="/Fe${executable.name} /MT /link advapi32.lib user32.lib jvm.lib"></property>
- <!-- The quotes must be double-escaped -->
- <property name="java.args"
- value="-DJAVA_ARGS="{ \"com.ociweb.jnb.july2004.myapp.MyMainClass\" }""></property>
- <property name="app.classpath"
- value="-DAPP_CLASSPATH="{ \"\\lib\\tools.jar\", \"\\lib\\${jar.name}\" }""></property>
- <property name="jdk.minor.version"
- value="-DJDK_MINOR_VERSION=\"4\""></property>
- <property name="jdk.major.version"
- value="-DJDK_MAJOR_VERSION=\"1\""></property>
- <path id="compile-classpath"></path>
- </target>
- <target name="setup" depends="set-properties">
- <mkdir dir="${dest}/classes"></mkdir>
- <mkdir dir="${dest}/lib"></mkdir>
- </target>
- <target name="compile" depends="setup">
- <javac srcdir="${src}" destdir="${dest}/classes">
- <classpath refid="compile-classpath"></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>
- <env key="LIB"
- value="${vctk}\lib;${ms.platform.sdk}\lib;${java.sdk}\lib"></env>
- <env key="CL"
- value="-DWIN32 -DJAVAW ${jdk.major.version} ${jdk.minor.version} ${java.args} ${app.classpath} ${link.opts}"></env>
- <arg value="java.c"></arg>
- <arg value="java_md.c"></arg>
- <arg value="myapp.res"></arg>
- </exec>
- </target>
- <target name="jar" depends="compile">
- <jar destfile="${dest}/lib/${jar.name}" basedir="${dest}/classes"></jar>
- </target>
- <target name="deploy" depends="jar, compile-launcher">
- <copy file="${src}\launcher\${executable.name}" todir="${deploy}\jre\bin"></copy>
- <copy file="${dest}\lib\${jar.name}" todir="${deploy}\jre\lib"></copy>
- </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 likejavaw
. This means that it is a Windows program, not a console program. In other words, it will launch withWinMain()
instead ofmain()
. 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 ajava.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 incom.foo.bar.myapp
, then that should be the valueJAVA_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
- Unzip the
resourcesJuly2004.zip
file to a directory (referred to as the "project" directory later) on your system. - Install Apache Ant 1.6.1.
- Install the Microsoft Visual C++ Toolkit on your system
- Install the Microsoft Platform SDK on your system
- Locate the file
cvtres.exe
on your system. On my system it is located inc:\windows\Microsoft.NET\Framework\v1.1.4322
. Add this directory to your path. - Copy the contents of src\launcher (found in the src.zip file located in your JDK home directory) into the
project\src\launcher
directory. - Copy the jre directory of your JDK installation into the
project\deploy
directory. - Edit the build.xml file, located in the
project
directory, and change the following properties:vctk
- set this to the directory where the Visual C++ Toolkit is installedms.platform.sdk
- set this to the directory where the Microsoft Platform SDK is installedjava.sdk
- set this to the directory where the Sun Java SDK is installed
- 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. - Run the ant script.
- Test the launcher by navigating to the
myapp.exe
file in theproject\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
- [1] Apache Ant
http://ant.apache.org - [2] Microsoft Visual C++ Toolkit 2003
http://msdn.microsoft.com/visualc/vctoolkit2003 - [3] Microsoft Platform SDK
http://www.microsoft.com/msdownload/platformsdk/sdkupdate - [4] Dr. Dobb's Journal; May, 2004, "C/C++ Compiler Optimization", Andrew Wilson
- [5] Dr. Dobb's Journal; November, 2003, "C++ Compilers & ISO Conformance", Brian A. Malloy, James. F. Power, Tanton H. Gibbs
- [6] Documentation for the Visual C++ compiler and libraries online:
http://msdn.microsoft.com/library/en-us/vcedit/html/vcorivisualcmainnode.asp - [7] exe4J commercial java launcher maker
- [8] JEXECreator commercial java launcher maker
- [9] Janel open-source java launcher maker
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.