By Marcus Daley
The Win32 API is common to all of Microsoft’s modern operating systems. However, there are some important features that can only be found under Windows NT. One of these features is the notion of a service, commonly referred to as an NT service. Typically, an NT service is a long-lived process that is launched when the operating system boots up. Some examples of these services are FTP servers, HTTP servers, printer spoolers, and domain name servers.
The mechanism for an operating system starting long-lived processes has its roots in UNIX, and that operating system’s concept of a daemon. A daemon is a system process that runs continuously while a computer is turned on. Daemon processes usually perform specialized services; hence, the daemons running under Windows NT have been dubbed NT services. Ideally, any process that must run constantly while an NT machine is active should be built as a native service.
An important difference between an NT service and an ordinary application is that services do not have user interfaces. Instead, NT services perform system tasks that don’t require desktop interaction. An FTP server is a common example of this sort of service. Although a user interface is required when users are added and removed from an FTP server, 99 percent of the time no user interface is necessary. This makes an FTP server an ideal candidate as an NT service. As a service, the FTP server will be launched when the operating system is started, and its process will remain unseen by any Windows desktop users. The only way an NT service can be “seen” is through Windows NT Task Manager, or the Services dialog box, accessed from the Control Panel.
Even with something as specialized as an FTP server, a user interface is important. The canonical way for a user to communicate with a service is via a Common Object Model-based client. The COM-based user interface should be a separate process that communicates to the FTP service when adding and removing users. When the user is done with the COM client they will shut down the user interface process, leaving the FTP service running in the background. This sort of client server behavior is common among NT services for services that require a user interface.
Another important feature of NT services is their ability to run under specific security restrictions. NT services benefit from the NT security model, where each service can be launched under a specific NT user identity. This feature can restrict services to a limited set of behaviors defined for an individual user. This is a powerful and enabling feature. An example of its usefulness is when a service should have access to a limited set of directories on the file system. To achieve this, the administrator of the NT system can set up a user account with a set of limitations. Then at system start-up, any specific NT service can be executed as that user, safely restricting file access for the service.
Service InternalsWhen looking at the internals of an NT service — its essence, if you will — a surprising level of simplicity is revealed. For the most part, NT services are plain command-prompt-based executables with well-known functionality. The operating system’s service control manager “knows” these functions and calls them when the user or system wants to Start, Stop, or perform any action with the service. When started, the service will usually launch one or more threads to perform system tasks. These threaded tasks can perform such things as watching an Internet socket for incoming requests, logging events to a text file on disk, spooling print jobs, etc.
There is a common set of defined actions that can be performed with an NT service. The Win32 API defines these functions (see Figure 1), and most services support them.
Stop | All services should be stoppable by the user. To support this level of functionality, the user should be able to stop the service, and the executable should stop running. |
Shutdown | All services should support the shutdown functionality. This is similar, and in addition to, the Stop function. A Shutdown means that the system is being shut down or restarted. |
Pause | The service can be temporarily halted from performing meaningful processes. The process will probably remain in the background, but it should do nothing. (The sample program in this article breaks this rule because it’s demonstrating functionality.) |
Continue | This function enables the service process to resume its work. The service must support the Pause function. After the Pause function has been called, the service should be able to start processing again. |
Figure 1: Functionality common to Windows NT Services.
Building Services with JavaThrough the Service class included in the com.ms.service package, the Win32 API functions can be easily, and eloquently, implemented in Java. In fact, it is so simple to write an NT service in Java that the average programmer can write one without a clear understanding of how it is implemented within the Win32 API. This doesn’t negate the importance of understanding the Win32 API, or how a service interacts with the NT operating system. It only means the Java programmer can spend more time worrying about design issues, rather than the implementation problems usually associated with other non-garbage collected languages.
The Win32 API entry points for handling these functions in Java are member functions. These functions are included in the com.ms.service.Service class, and are listed in Figure 2. Although these member functions aren’t designated as abstract in the com.ms.service.Service class, most must be implemented in each service. The member functions that are implemented will vary, depending on the functions the service claims to support when it’s installed.
handleShutdown | Called when the OS is attempting to shut down or restart the entire system. Threads running as a part of this service should be stopped in this function. This function should return true if successful. |
handleStop | Called when the user requests to stop a service that’s been started. Threads running as a part of this service should be stopped in this function. This function should return true if successful. |
handlePause | Called when the user requests to pause a service that’s been started. The implementation of this function should return false if successful, and include a call to the setPaused function. |
handleContinue | Called when the user requests to continue a service that has been paused. The implementation of this function should return false if successful, and include a call to the setRunning function. |
Figure 2: com.ms.service.Service class functions.
The Java Service ConstructorAnother important function, and the last function required to implement a working Java service, is the constructor. The constructor is important because it acts as the main starting function for the service (analogous to Java’s static main method). In other words, the constructor performs initialization of variables, and starts the service threads.
There are two types of constructors that can be implemented by a Java service: the default constructor, and an additional constructor that accepts a String parameter. The String parameter contains additional command-line arguments the service will want to process:
public class ClassName(String[] args) {}
It’s important to remember that the standard functionality of the Win32 API is supported by implementing the com.ms.service.Service handle member functions, and doesn’t need to be re-implemented via String arguments to the constructor. This includes several default command-line arguments. The super class will process the default arguments before they make it to the child class constructor. This makes supporting service functionality especially convenient in Java. The command-line arguments automatically supported by the super class (com.ms.service.Service) will be discussed later in the section titled “Installing an NT Service.”
Before we discuss the physical installation process, it’s important to understand how the installation is handled programmatically. Each NT service requires a special installation so the operating system knows the service exists. If it isn’t installed, the operating system’s service control manager won’t have knowledge of the new service, and the service’s binary executable will remain unused. Using most languages, implementing the necessary Win32 API functionality to install a service can be problematic. With Java, however, the installation semantics have been simplified.
For proper installation, a call to the setRunning member function must be in the constructor. Everything else is taken care of through the existence of a constructor, and through the implementation of the appropriate handle functions. The setRunning function has been overloaded to accept no arguments, or an Integer argument.
Under most circumstances, the setRunning(int controls) function should be used. It accepts any combination of the values shown in Figure 3. When you pass an Integer value to the setRunning(int controls) function, the service declares to the operating system’s service control manager what actions it does, and does not, support. The result is reflected in the Services dialog box, so it can be seen by a user when stopping or starting a service.
ACCEPT_PAUSE_CONTINUE | Indicates that the service supports the handlePause and handleContinue functions, i.e. the service can be paused and then continued. |
ACCEPT_SHUTDOWN | The service supports the handleShutdown function by allowing the service to be shut down with the rest of the system at OS closing time. |
ACCEPT_STOP | The service supports the handleStop function; the service can be stopped after it’s been started. |
Figure 3: Control code arguments for the setRunning function.
Service Source CodeTo illustrate how to write an NT service in Java, source code accompanies this article that demonstrates a simple Java service that logs a hard-coded message to a text file every few seconds. (The example is available for download; see the end of this article for details.) This service can be installed, started, stopped, paused, and continued. Once you perform a build on the demonstration source code, continue this article to see how to make it an executable.
Unless you’ve already done so, it will be necessary for you to install the com.ms.service package to make it available to Visual J++ 6.0 (VJ6) Here are the steps:
Install the Microsoft SDK for Java 3.1. It’s available for free download at http://msdn.microsoft.com/visualj/downloads/sdk.asp.
Locate the service.zip file in the SDK-Java.31\Bin\jntsvc directory where you have installed the SDK.
Unzip service.zip into your \Winnt\Java\TrustLib directory.
Listing Six contains the source code for a Java service that extends com.ms.service.Service. Listing Seven contains the source code for the demonstration ServiceThread class that writes to the file on disk. This class implements Runnable and is a separate, native, Java thread that must respond to Stop, Pause, Start, or Continue requests (depending on the user and the system).
Looking at the code in Listing Seven, you can see how important it is to understand threading under Java when creating an NT service. The demonstration ServiceThread class is relatively simple; it has no thread synchronization issues. For most multi-threaded services, this process is much more complex. Consequently, abiding by strict threading rules is important in Java programs and especially in NT services. If the service code isn’t written carefully, users of the Windows NT machine will find the service consumes vital system resources.
A multi-threaded NT service must be keenly aware of the amount of system resources it is using. A service should be transparent to all users. If not, the system could show 100-percent CPU use with no applications visible on the desktop. Obviously, users will wonder what’s going on. Again, resource efficiency is of paramount importance with NT services.
Service AssassinsOne result of improper threading is when the user sends a Stop message to the service, and then waits indefinitely for the service to terminate. This happens when a service thread is deadlocked, or never receives the Stop message. To ensure the user’s request for a Stop is satisfied, the com.ms.server.Service class has a set of member functions that implement an assassin. An assassin watches a timeout value set on the service for pending function requests. If a function request isn’t completed before the timeout occurs, the assassin kills the service. Assassins enforce compliance with user and system requests.
A good example of when an assassin is important is when the system is shutting down. If a running NT service never receives a message to shutdown, system deadlock can occur, and the system may never shutdown. In this case, the well known function handle, handleShutdown, is always called. However, it is still possible the thread will never get the communicated request to shut down. If a Java assassin is activated, the service will be terminated when the timeout period expires. This is important when the system is shutting down, because it needs to finish closing other system-related tasks. You can imagine an angry user seeing a message that Windows is shutting down for 20 minutes, or more, because of a deadlocked service.
One way to let the system know a service is processing and hasn’t become deadlocked is by using the CheckPoint function. This function temporarily stops an assassin from terminating the NT service before it’s completed. In other words, the CheckPoint function resets the assassin’s timeout value. The overloaded CheckPoint function can accept an integer or no argument. The CheckPoint(int waithint) function should almost always be used. It tells the service control manager how long the NT service expects to continue processing before it’s complete, or at another checkpoint.
Listing Six only demonstrates one CheckPoint(int waithint) call made in the constructor. When several items are being processed in a service handle function or the constructor, a call to CheckPoint should occur frequently and at regular intervals. Be aware that an infinite loop filled with CheckPoint calls can also deadlock a service.
By default, assassin functionality is turned on to ensure shutdown of NT services derived from com.ms.service.Service. It’s easy to change assassin behavior directly so they can even be turned off. If a service disables its default assassin, it’s then the responsibility of the child class to kill itself when necessary. This means the service must implement assassin behavior on its own. This can be dangerous if deadlock occurs because the service cannot die. Therefore, use extreme caution when disabling or otherwise handling assassins in relation to any Java NT service.
To demonstrate a simple and safe use of assassins, a call to setAssassinTimeout has been added to the original constructor in Listing Six. The call to setAssassinTimeout changes the default timeout from 30 to 60 seconds. The revised constructor is displayed in Figure 4.
public SampleService(String[] args)
{
// Tell the system it expects to finish this operation in
// approximately 1,000 milliseconds.
CheckPoint(1000);
// Set the service assassin to execute in 60 seconds,
// rather than the default 30 seconds.
setAssassinTimeout(60000);
// Declare which functions are supported by this service.
setRunning(
ACCEPT_SHUTDOWN | ACCEPT_PAUSE_CONTINUE | ACCEPT_STOP);
// Declare the new service thread and start it. This
// means logging will continue until the service is
// shut down or stopped.
ServiceThread =
new Thread((Runnable) new ServiceThread());
ServiceThread.start();
}
Figure 4: The revised SampleService constructor.
Although assassins are important, they should never become the main mechanism used for terminating a service. There are many reasons why assassins should only be used as a last resort. One reason is that a sudden termination can’t ensure that existing COM objects are properly released, or DLL’s dereferenced. Another reason is that in-memory data structures might not have been written to disk for persistence; if an assassin kills the service, that data will be lost. Also, if the user has to wait 30 seconds for an assassin to process, then the usefulness and reliability of the service might be questioned. Assassins should only be used as an emergency death sentence if a service process has gone out of control, and is no longer responding on its own.
Event LoggingOne way to debug and monitor the state of an NT service is through an event log. Event logs are viewed in the Event Viewer application (see Figure 5), which is normally found among the Administrative Tools (from the Start menu, select Programs | Administrative Tools | Event Viewer). With the Event Viewer, the user can observe three log files on the system: the System Log, the Security log, and the Application Log.
Figure 5: Windows NT Event Viewer with the System Log displayed.
/base:dir | Specifies the location of the root directory. It represents the starting location where jntsvc looks for the class files that have been included in the service. |
/out:filename | Specifies the filename of the executable file to be generated by jntsvc. |
/r | Indicates that the jntsvc tool recursively iterates through subdirectories when looking for class files that need to be included. This is often used with the /base parameter and it is turned off by default. |
/r- | This is the exact opposite of the /r parameter. This parameter shuts off subdirectory recursion if the /r parameter has already been invoked. |
/v | Turns on verbose mode, causing it to print more detailed output. |
/svcmain:classname | Indicates which class extends the com.ms.service.Service class. This parameter is case sensitive. If the specified subclass is a part of a package then package names must be separated with periods. |
/eventsource | Sets the event source name. This is the name that will appear in the event viewer when an event is written. |
/servicename | Sets the name that the service will be registered under. |
/displayname | Sets the name that will appear in the service control panel. This name should be intuitive and meaningful so that users will know which service it represents. |
Figure 6: jntsvc command-line parameters.
Typically, NT services make use of the Application Log. This is where services and other applications log errors or other information the user might find useful. For example, if an FTP service fails to start because an Internet connection cannot be initiated, then an error message should be sent to the Application Log detailing the state of the service and why it failed.
An easy mechanism for logging to the Application Log has been provided to Java NT services through the use of the java.lang.System class. One purpose of the System class is to manipulate data processed through the standard input, standard output, and error output streams. In Java NT services, data that is sent to the standard output, or error output streams, will be reflected in the Application Log. This makes sense because an NT service is a console-based application that would normally make heavy use of the standard streams. Because a service doesn’t have a visible output display, the output and error streams are instead routed to the Application Log so users can view the data.
Messages sent to the Application Log receive extended information that includes the date, time, source, user, etc. This data will accompany all log messages and help the user debug or monitor the state of an NT service. This also makes logging messages more useful and informative for users of a service.
An example of using the standard output and error output streams can be seen in Listing Six. A call to the standard output stream was made in the constructor that sent a message detailing the sample service was started. Each time the service starts, this message will appear in the Event Viewer and be accompanied by a blue information icon in the date column. Also, a call to the error output stream was used in the handleStop function. When the service is stopped or shutdown, this message can be seen in the Event Viewer accompanied by a red stop icon in the date column.
The jntsvc ToolNow that the demonstration Java NT service has been built (i.e. the *.class files have been created), the next step is to make it an executable. Making the Java class an executable is simple. Microsoft has supplied a tool named jntsvc (Java to NT service) that takes the class files and generates a service executable. jntsvc is similar in usage and purpose to the jexegen tool. jntsvc is a console program that accepts a series of command-line statements, and then generates an executable based on the command-line arguments. After using jntsvc, the resulting service executable is self-contained, self-installing/uninstalling, and redistributable.
The command-line parameters supported by the jntsvc tool are shown in Figure 6. Although there are several command-line parameters, the same combination of parameters will usually be used. Once a useful template has been created, it will be used repeatedly. This holds true when using the jntsvc tool. The command in Figure 7, for example, shows how to take the sample Java NT service classes shown in Listings Six and Seven and generate them into a working NT service executable using the jntsvc tool.
Figure 7: An example jntsvc command.
The Java class derived from the com.ms.service.Service class in Listing Six was supplied as the argument to the /svcmain parameter in Figure 7. This is, perhaps, the most important parameter because in order to execute, jntsvc must know what service class extends the com.ms.service.Service class functionality.
The same argument, SampleService, was supplied for the /eventsource parameter. This is because the /eventsource argument is the name that will appear in the Source column of the Event Viewer. When events are placed in the Event Viewer, most readers of the Event Viewer want to see the name of the executable that generated the event. For this reason, the same name that appears as the argument in the /svcmain parameter and the /out parameter will be used with the /eventsource parameter.
The /servicename and /displayname parameters are also the same. Both of these parameters should contain meaningful names that the service is registered under, and that will appear in the Services dialog box.
The final arguments are the class files from which the executable are generated. Obviously, it’s possible to include several class files, which is useful if the service involves several class files created solely for service functionality. This improves performance since the class information comes from the executable rather the class path. Again, the command shown in Figure 7 is an example of this.
The ServiceThread class and the SampleService class are also included in the generation of the executable. This is important because neither class is part of a well known package. Unless the directory where the classes formally reside is in the class path, the service executable won’t function unless they’re both included in the creation of the executable.
Installing an NT ServiceThe final step, after running the jntsvc command just demonstrated, is to install the service. There are several ways this can be done. One feature provided by the jntsvc tool is that the new service executable has a /help facility compiled into it. At the command line, type the following:
SampleService /help
to generate a report of available commands (see Figure 8).
Figure 8: Using the /help parameter to list available commands for the example service.
These commands can be sent to the new NT service executable. The simplest thing that can be done with the new service is to install and uninstall it. By using the /install switch the service will be registered with the service control manager, and the display name will appear in the Services dialog box.
If the service has already been installed then it can be uninstalled using the /uninstall switch. The /uninstall switch will promptly remove the display name from the Services dialog box, and unregister the service.
An advanced action that can be performed with the new service executable is to register it under a user account. This is important if the service needs to run under certain security restrictions. A server executing as an anonymous FTP server is an example of this security feature. It would be extremely undesirable to let unauthorized FTP users have access to important system directories. Placing the FTP service under a strict NT user account can eliminate this problem. Adding user restrictions can be achieved as easily as installing or uninstalling the service. The command-line parameters, /username and /password, have been supplied to support this functionality. They work in tandem; if one of them is supplied, so must the other.
Of course, not all of the desired functionality has been included in the executable by default. Obviously, many custom services will need the ability to accept proprietary parameters. This ability has been accounted for with the inclusion of the special com.ms.service.Service constructor discussed earlier in this article. The purpose of the String parameter is to allow custom parameters to be passed to the service when it is started. These additional parameters can be provided as arguments to the /params statement when the service is installed. No matter what additional parameters are included, however, they will be meaningless unless the service has been specially programmed to understand them.
Now that our example service has been installed, let’s take a look at the Services dialog box (see Figure 9). The display name specified with the /displayname parameter should appear in the Service column. This new service should respond to Start, Stop, Pause, and Continue commands, depending upon its current state; actions that are unavailable will be “grayed out.” It’s important to remember the Control Panel functions are supported because they were declared with the setRunning function in the SampleService class’s constructor.
Figure 9: After installation, the example service appears in the Services dialog box.
ConclusionCreating Java-based, Windows NT services is simple and straightforward. Because of Java’s garbage collecting design and object-oriented nature, it’s a good choice when building a long-lived server — especially when developing system-level programs that require a clean memory model and clear semantics. With a bit of experimentation and good threading practice, Java programmers can create robust NT services in no time.
Marcus Daley is a software engineer at Webridge, building Java applications for customer, partner, and channel management. He has been involved with Java development since its birth. His first major accomplishment with Java was in 1995 while working for Utah State University where he pioneered research into 3D modeling applications written in Java and VRML. Later, he joined a startup GIS company based in Northern Utah where he developed Java-based Intranet solutions for small businesses and local governments. Since then he has consulted several companies throughout Northern Utah and worked together with many respected professionals in the Java community. Marcus lives in Portland, OR, and can be reached at marcus@mdaley.net.
Begin Listing Six — SampleService.java/**
* This class extends the com.ms.com.Service class. This
* class is intended to demonstrate how to build an
* NT service using Java.
*/
import com.ms.service.*;
public class SampleService extends Service
{
/**
* This variable holds the thread that will be started
* and do the logging work.
*/
static Thread serviceThread = null;
/**
* This constructor registers and starts the service.
*
* @param args is an array containing any additional
* command-line parameters that were passed to the
* service upon startup.
*/
public SampleService(String[] args)
{
// Telling the system that it expects to finish this
// operation in approximately 1,000 milliseconds.
CheckPoint(1000);
// Declaring what functions are supported by
// this service.
setRunning(ACCEPT_SHUTDOWN | ACCEPT_PAUSE_CONTINUE |
ACCEPT_STOP);
// Declare the new service thread and start it. This
// means that logging will continue until the service
// is shut down or stopped.
serviceThread =
new Thread((Runnable) new ServiceThread());
serviceThread.start();
// Writing a message to the Application Log indicating
// the service has been successfully started.
System.out.println(
"The Sample Service Was Started Successfully!");
}
/**
* This function will be executed when the service has
* been requested to continue.
*/
protected boolean handleContinue( )
{
// Setting this variable to false will allow logging
// to resume.
ServiceThread.PAUSE = false;
// This tells the service control manager the service
// is running again.
setRunning();
return false;
}
/**
* This function will be executed when the service has
* been requested to pause.
*/
protected boolean handlePause( )
{
// Setting this variable to true pauses logging.
ServiceThread.PAUSE = true;
// This tells the service control manager the service
// has successfully been paused.
setPaused();
return false;
}
/**
* This function will execute when the service
* shuts down.
*/
protected boolean handleShutdown( )
{
handleStop();
return true;
}
/**
* This function will execute when the service has been
* requested to stop all functionality.
*/
protected boolean handleStop( )
{
// In this try/catch, the thread is told to stop. After
// setting STOP to true, it will be deliberately killed
// after 10 seconds.
try
{
if (serviceThread != null)
{
ServiceThread.STOP = true;
Thread.sleep(10000);
serviceThread.stop();
}
}
catch (Exception e)
{
}
serviceThread = null;
// Writing a message to the Application Log indicating
// the service has been successfully stopped.
System.err.println(
"The Sample service has been stopped!");
return true;
}
}
End Listing Six
Begin Listing Seven — ServiceThread.java
/**
* This class implements the Runnable interface so it can
* be a Java thread. This class is intended to demonstrate
* how to build an NT service using Java.
*/
import java.io.*;
public class ServiceThread implements Runnable
{
/**
* This static variable indicates whether the service
* wants to shut down or stop.
*/
public static boolean STOP = false;
/**
* This static variable indicates whether the service
* wants to pause or continue.
*/
public static boolean PAUSE = false;
/**
* This function is required by the Runnable interface.
* This functions creates a log file and logs text
* strings describing the current state of the service.
*/
public void run()
{
// A few variables are created for writing to the
// log file.
BufferedWriter outputStream = null;
try
{
File outputFile = new File("c:\\", "test.log");
outputStream = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(outputFile)));
// This string will be logged first, indicating the
// service has started.
outputStream.write("Starting log...");
// This loop will run until the STOP variable is set
// to true.
while (!STOP)
{
// This loop will run until the STOP or PAUSE
// variable is set to true.
while (!PAUSE && !STOP)
{
outputStream.newLine();
// This line will be written continuously until a
// pause or stop is triggered.
outputStream.write(
"WAITING FOR A PAUSE OR STOP");
outputStream.flush();
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
}
}
outputStream.newLine();
// If a stop or pause has been triggered, this line
// will appear in the log.
outputStream.write("Paused or stopped!");
outputStream.flush();
try
{
Thread.sleep(1000);
}
catch (InterruptedException ie)
{
}
}
// This line will be written when the service has
// been stopped.
outputStream.newLine();
outputStream.write("Closing log...");
outputStream.close();
}
catch (IOException ioe)
{
if (outputStream != null)
{
try
{
outputStream.close();
}
catch (IOException ioe2)
{
}
}
}
}
}
End Listing Seven