Running a Java 2 Application as an NT Service
Revised - August 25, 2000
Running an application as a service offers several advantages. Once installed, execution is automatic and transparent to any user, and a user does not have to log in for any service to start. On most UNIX-based servers, the process for installing an application to run in this fashion is straightforward and accomplished using startup scripts that can be named to create a prescribed order of execution. NT, on the other hand, manages services that are defined and described in the registry. Java server applications are good candidates for NT services. However, installing a Java application as a service requires building an executable file in native code that, in addition to running and interacting with the Java application itself, calls several Win32 API functions used with services and the registry.
A simple, yet effective NT service framework is described in the sample source code for Visual C++ provided on the Microsoft MSDN CD. However, in order to better accommodate our particular application, significant code revisions and additions were made to the sample. The Java Tutorial from JavaSoft provides a C program to invoke the Java 1.1 Virtual Machine (JVM) using the Java Native Interface (JNI.) While portions of our launcher are based largely on this example, the code provided here has been modified to work within our service framework, and also to comply with JNI changes introduced in Java 2
In order to demonstrate the service launcher, we will also create a simple Java server and client that use Remote Method Invocation (RMI.) Using the revised NT service framework and JVM invoker, we will create an easily customized service installer/uninstaller. We will use it to install our simple RMI server application as an NT service. This server will log start/stop events to a file in a specified working directory.
Using this launcher requires no native coding in the Java application. The Java classes can be 100% Pure Java, and run conventionally on any Java 1.2 platform. NT-specific code is entirely contained within the launcher.
A copy of this file, and all of the source code and binaries for the complete example are available for download in a zip archive (about 67 KB) Unpack using a zip utility that supports long filenames (such as Winzip) and be sure to "use folder names" to recreate the zip's directory tree when you unpack it.
The bulk of code to create our NT Service is contained in the source files
For nearly all purposes, the only code that will require changes before building the launcher for a specific Java application is service.h.
Service.h is the code unit that is modified for a specific application. There should be no need to change the other source code files.
In addition to containing application-specific definitions, service.h also prototypes the utility functions in service.c, and the service start and stop functions that are implemented in javaserver.c.
Since our example will create an RMI registry if one is not running, we do not require a dependency for rmiregistry running as a service (as with previous versions of this tutorial.).
Also note that in SZPARAMKEY, "LauncherTest" corresponds to whatever value SZSERVICENAME takes. When you change SZSERVICENAME you must change SZPARAMKEY accordingly.
Finally, note the path separators used to define the package-qualified names of the sample Java application RMIDemoImpl and the SCMEventManager classes. (If your application does not implement SCMEventManager, define SZSCMEVENTMANAGER as "".)
Service.c provides a main entry point, that branches to appropriate functions based on options supplied on the command line. These options permit installation or removal of the service, or running the program as a console application (useful for debugging.)
The code also contains the ServiceMain function, which is the actual entry point for the service itself. Also implemented is the required control handler. Some utility functions are provided, including those for reporting service status to the service control manager, formatting and displaying error messages, and handling Ctrl+C or Ctrl+Break to simulate stopping the service if run from the console.
Since there is no direct mechanism for passing arguments to a service when it is started, service.c calls functions from parseargs.c and registry.c to parse, save and retrieve runtime arguments for the Java application as values in the registry, under the service's key.
These functions are explained in the source code documentation.
When the service is installed, a registry key is created by the system at
Values are saved under this key that contain information about the service, such as how it is started, the displayed name, dependencies, etc.
In order to start the service automatically with certain arguments that are used by the JVM and the Java application's main class, we will provide these arguments when the service is first installed. Once the service registry key is created by the system, we will create a new subkey named Parameters, and under Parameters we'll create a value named AppParameters. AppParameters will be used to save the argument list for our Java application as a space-delimited string (REG_SZ) value.
After installation, when the service is started, it will check the registry (unless it is running in console mode) and pull out the AppParameters value. It will then parse the string into separate arguments that will be passed to the invoked Java app (or also used as arguments to the JVM itself, as shown in the next section.)
Refer to registry.h for detailed documentation.
Parseargs.c contains functions to manipulate program arguments. It contains the functions
Because of the way a Java application can be launched, special handling of runtime arguments is necessary when the service is installed or run from the command line. It is necessary to differentiate between arguments intended for the JVM, and arguments intended for the Java application. To accomplish this, JVM arguments are preceded with -D or /D, -X or /X, exactly as they are when running java.exe (Note that this switch is case-sensitive.)
For example, a typical JVM argument for an RMI server might be:
A special argument is the working directory for the application, which is prefixed with "wrkdir="
The functions in parseargs.c provide the special handling needed to separate JVM arguments from application arguments, and to convert argument arrays to space-delimited strings (for storing in the registry) and back into arrays (for retrieving from the registry.)
Refer to parseargs.h for additional information.
The code to invoke the Java server application is contained in
In this code, the JNI Java invoker is started in a separate thread by the ServiceStart function, defined in service.h. ServiceStart is called from the service_main and runService functions in service.c.
When the service runs, either from the system or from the command line, the ServiceStart function is called. ServiceStart parses the arguments that are passed to it, separating JVM arguments and application arguments. It spawns a thread to invoke the JVM, which will run the Java application, and then simply waits for a signal to stop running.
Javaserver.c also contains the ServiceStop function. This function is called when the service is stopped. It releases any storage that was allocated when parsing the arguments and signals the ServiceStart function to stop waiting. This function will also pass a SERVICE_STOPPED event to an SCMEventManager class in your Java app, if present.
Javaserver.c contains the following functions:
To demonstrate using the launcher, we've created a small client/server Java system that uses RMI. The Java sources consist of:
The Java sources are contained in the <demo root>/demo directory of the distribution. These compile to classes in the com.kcmultimedia.demo package. RMIDemoImpl is the server-side application. RMIDemoClient is, obviously, the client-side application.
The demo applications do not perform any truly significant tasks, other than providing a simple vehicle that we can use to demonstrate use of the launcher to install RMIDemoImpl as an NT service. We can then run RMIDemoClient, which will communicate with RMIDemoImpl using RMI, and display returned messages in an AWT Frame.
We're not going to go into great detail explaining compilation of the Java classes. Chances are, if you are reading this, you've probably done this sort of thing a few times. However, do not forget that after compiling, use rmic to generate the stub and skeleton classes for RMIDemoImpl that are required for RMI.
A makefile is provided to simplify the build process. In the makefile, be sure to change the BIN definition to point to your own JDK 1.2 installation. In a console window, run the makefile using nmake. Running the makefile will build the demo package, generate stub and skeleton classes, and jar the package into rmidemo.jar
If you've downloaded the files to accompany this paper, the demo classes are pre-compiled and saved in rmidemo.jar. You will soon see exactly what the demo does.
In the makefile, be sure to change the JDK definition to point to your own JDK 1.2 installation. In a console window, run Vcvars32.bat (in VC++'s bin directory) to set up the environment for command line compilation. Then, run the makefile using nmake.
We've provided a compiled launcher, javaserv.exe, that has been hardcoded (in service.h) to run our demo server application, RMIDemoImpl.
Once the launcher has been built for your particular application, installation of the service is accomplished by running the launcher with appropriate options and runtime arguments. However, first check that the launcher runs properly from the console before installing it as a service. This will confirm that runtime arguments are correct and that the program successfully starts.
The javaserv.exe program that we will use to install our service requires the presence of a dynamic link library, jvm.dll. This file is present in the jre/bin/classic directory of the JDK 1.2 or JDK 1.3 installations, and also in the jre/bin/hotspot directory of JDK 1.3 . Important: Do not move jvm.dll, or save a copy into your application's directory. Either copy javaserv.exe into the JDK subdirectory containing jvm.dll (hotspot or classic), or ensure that your system environment PATH contains jvm.dll.
Also, for the sake of simplicity, we will assume that the RMI server and client will both be running locally on the same machine.
If all is well, you will see the message
RMIDemoServer bound in RMI Registry
This command will simulate running the service LauncherTest, with the -D arguments passed to the JVM, and other arguments passed to the Java application that is run in the JVM. The above -D arguments are typical JVM arguments, setting the RMI codebase and appending the application classpath to the default classpath. If you examine RMIDemoImpl.java, you will see that argument 'localhost' is an application argument used when rebinding the application to the RMI registry. Finally, the working directory is set to the specified location. The sample server will log start/stop events to a file named "RMIDemo.log" located in this directory. (If a working directory is not specified, NT will use Winnt\system32 by default.)
In an actual RMI deployment, you'd probably substitute HTTP protocol for the FILE protocol used in this example to permit remote access from other machines in the network. You might also provide a security policy file. For details, refer to the RMI documentation that accompanies the JDK.
If the launcher runs successfully from the console, you are ready to install it as a service. If still running, stop the console versions of RMIDemoServer and rmiregistry.exe. At the system prompt, enter a command based on that below:
javaserv -i -Djava.class.path=<demo root>/demo/rmidemo.jar -Djava.rmi.server.codebase=http://<url to rmidemo.jar> <rmiregistry hostname> wrkdir=<demo root>\demo
For example, lets say that our machine is goober.rfd.net. We've mapped <demo root>/demo to the web server alias "demo". The command will be
javaserv -i -Djava.class.path=<demo root>/demo/rmidemo.jar -Djava.rmi.server.codebase=http://goober.rfd.net/demo/rmidemo.jar goober.rfd.net wrkdir=<demo root>\demo
Using this command, the service will install itself. It will not start execution of the application until either the computer is restarted, or the service is started from the Control Panel's Services applet.
If a web server is being used to deliver objects, then the web server service should be named as a dependency in service.h. The codebase should be mapped appropriately in the web server's configuration so that the codebase URL to rmidemo.jar is valid.
Once you have your new LauncherTest service installed and started, you can run the RMIDemoClient application to test it.
Open a DOS window, and make <demo root>/demo the current working directory.
At the system prompt, enter
<java root>\bin\java -cp .\rmidemo.jar com.kcmultimedia.demo.RMIDemoClient localhost
In the client, enter a simple yes-or-no question in the upper TextField, and press the "Get Answer" button. The RMIDemoImpl will pretend to be a psychic, and give you its answer to your question, via RMI.
To remove the service, run the launcher with the -r option
It is not necessary to stop the service prior to removal. The removal process will programmatically stop a running process if necessary, and then remove the service.