Running a Java™ 2 Application as an NT Service

Copyright 1998 / Revised - August 25, 2000

by Rocky Fikki / Bill Giel / KC Multimedia and Design Group

Features

  • Complete Java and C code samples for installing and launching a pure Java 2 application as an NT service

  • new2.gif (144 bytes)Code samples  pass -D and -X arguments to the JVM

  • Works with JDK 1.3!

  • Code that demonstrates passing native SERVICE_STOPPED events from NT's Service Control Manager to a pure java listener class that can be built into your Java application. (No JNI calls are needed in the Java application.)

  • Code samples  permit setting a working directory for the java application.

Abstract

This paper describes building an executable launcher for a pure Java 1.2 or 1.3 server application that permits self-installation as a Windows NT service. It also shows how to save and retrieve runtime arguments in the Windows registry, invoke the Java Virtual Machine (JVM) in a separate thread using the Java Native Interface (JNI) and pass an NT service event to Java using JNI . Full Win32 C source code for the launcher,  Java sources for a sample Java server, and binaries for a working example are provided.

Overview

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.

Download

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 Service Framework

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

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.

//==========================================
//TO DO: Change these definitions for your application
//==========================================
// internal name of the service
#define SZSERVICENAME "LauncherTest"
// displayed name of the service
#define SZSERVICEDISPLAYNAME "Launcher Test"
// list of service dependencies - "dep1\0dep2\0\0"
// This example has no dependencies
#define SZDEPENDENCIES "\0\0"
// Main java class
#define SZMAINCLASS "com/kcmultimedia/demo/RMIDemoImpl"
// Service TYPE
#define SERVICESTARTTYPE SERVICE_AUTO_START
// Path to Parameter Key
#define SZPARAMKEY "SYSTEM\\CurrentControlSet\\Services\\LauncherTest\\Parameters"
// name of the executable
#define SZAPPNAME "javaserv"
// Value name for app parameters
#define SZAPPPARAMS "AppParameters"
// Name of the Java SCMEventManager class
// Use empty quotes if the java app does not implement this class
#define SZSCMEVENTMANAGER "com/kcmultimedia/demo/SCMEventManager"

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

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.

Registry.c

Registry.c is used by service.c (and also in javaserver.c) to save and retrieve runtime arguments used by the invoked Java application in the registry. It contains the functions

int getStringValue(LPBYTE lpVal, LPDWORD lpcbLen, HKEY hkRoot, LPCTSTR lpszPath, LPTSTR lpszValue);

int setStringValue(CONST BYTE *lpVal, DWORD cbLen, HKEY hkRoot, LPCTSTR lpszPath, LPCTSTR lpszValue);

int makeNewKey(HKEY hkRoot, LPCTSTR lpszPath);

When the service is installed, a registry key is created by the system at

HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/SERVICENAME

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

Parseargs.c contains functions to manipulate program arguments. It contains the functions

LPTSTR getWorkingDirectory(DWORD dwArgc, LPTSTR *lpszArgv);

LPTSTR *getJavaArgs(LPTSTR *lpszArgs, PDWORD pdwLen, DWORD dwArgc, LPTSTR *lpszArgv);

LPTSTR *getAppArgs(LPTSTR *lpszArgs, PDWORD pdwLen, DWORD dwArgc, LPTSTR *lpszArgv);


LPTSTR* convertArgStringToArgList(LPTSTR *args, PDWORD pdwLen, LPTSTR lpszArgstring);

LPTSTR convertArgListToArgString(LPTSTR lpszTarget, DWORD dwStart, DWORD dwArgc, LPTSTR *lpszArgv);

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:

-Djava.rmi.server.codebase=http://gomer.mds.net/jdss/jdss.jar

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 JVM Invoker

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.

Javaserver.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:

void _CRTAPI1 invokeJVM(void *dummy)

VOID PassSCMEvent(int eventid)

VOID ServiceStart (DWORD dwArgc, LPTSTR *lpszArgv)

VOID ServiceStop()

RMI Example

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.

Build the RMI example

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.

Build the Launcher

After making any necessary changes to service.h, the launcher can be built with Visual C++ command line tools, using the makefile provided.

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.

Run the Launcher from the Console

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.

  1. Copy the sample launcher, javaserv.exe into the same directory as your jvm.dll file.
  2. Open a DOS window, and make this directory the current working directory.
  3. At the system prompt, enter the command:
    javaserv -c -Djava.class.path=<demo root>/demo/rmidemo.jar 
    -Djava.rmi.server.codebase=file:/<demo root>/demo/rmidemo.jar localhost
    wrkdir=<demo root>\demo

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.

Install and Start the New Service

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.

Important Notes

  • We've had reports that enabling "Interact with Desktop" may cause a service to shut down on logoff (apparently bypassing our custom console handler.)
  • If the service appears to start but a connection can not be made by the RMI client (and stopping the service causes an error to be logged in the Event Viewer), ensure you have not copied the jvm.dll out of the JDK directory into the directory containing javaserv.exe; instead, copy javaserv.exe into the JDK subdirectory containing jvm.dll, or ensure that your PATH contains jvm.dll.

Run RMIDemoClient

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.

Remove the service

To remove the service, run the launcher with the -r option

javaserv -r

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.

About the Authors
Bill Giel & Rocky Fikki are/were responsible for programming at the Java Applet Rating Service, and Bill Giel is a software development consultant for KC Multimedia and Design Group. Bill was also engaged in digital mapping, in-house software development and system administration for a small private business.Rocky Fikki is the CEO of KCMDG

Copyright © 1998 Revised 2000 by Rocky Fikki / Bill Giel/KCMDG. All Rights Reserved.