Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Q&A


Mar00: Java Q&A

Mike is an independent consultant/developer in Victoria, BC. He can be reached at [email protected].


A service in Windows NT (or "daemon" in UNIX parlance) is a program that runs in the background to do a specific task. Typically, NT services do not interact directly with users, but instead perform a regularly scheduled task (such as automated file transfers or backups) or interact with client applications via the network (such as a web or ftp server).

After writing numerous NT services for clients in C++, I wondered if it is possible to write an NT service in Java. I was partly motivated by the prospect of not having to be so vigilant about memory management (an important issue for continuously running programs), since Java has automatic memory management (garbage collection). Just as attractive was the idea that any service I wrote in Java would -- almost by definition -- be able to run on any computer.

Since Java is completely object oriented, I developed an object-oriented service framework in C++, so that I could easily map C++ service objects to a Java interface using the Java Native Interface (JNI). The source code for the service framework, along with an example service written using it, is available electronically; see "Resource Center," page 7.

NT Services

Because a service performs a unique task for a computer, there will only be one instance of the program running on a given computer (although services often spin off many threads to handle client requests).

An application can contain one or more services. For each service in the app, there must be a name unique to the service ("FTP," for instance) and a corresponding ServiceMain function. When an application containing one or more services is started by the operating system, it passes to the OS a list of the names of all of the services it supports along with the corresponding ServiceMain function of each service.

A ServiceMain function can have any name, but must be declared as in Listing One. The first argument to the ServiceMain function is always the name of the service. The remaining arguments are optional and can be specified under the services applet in Control Panel.

The way in which this list of services and their associated ServiceMains are passed to the OS is via the Win32 API call StartServiceCtrlDispatcher(SERVICE_TABLE_ENTRY *list);, where SERVICE_TABLE _ENTRY is a structure containing a pointer to a NULL terminated string containing the name of the service and another pointer that points to that service's ServiceMain function. The last entry in the list is followed by a SERVICE_TABLE_ENTRY whose members are all zero. Once StartServiceCtrlDispatcher is called by the app, the OS spins off a thread for each service specified in the list and calls each service's ServiceMain from each thread.

Inside each service's ServiceMain, a service registers a handler function for that service by calling RegisterServiceCtrlHandler, and passing it the name of the service and the address of the handler function. RegisterServiceCtrlHandler returns a service status handle, which is a handle that the service uses to refer to itself when it notifies the operating system of its current status (starting up, stopping, paused, and so on). The handler function can have any name, but must be declared, as in Listing Two.

The OS calls a service's Handler function when it wants it to stop, pause, or continue. The action that the OS wants the service to take is passed as the signal parameter to Handler. The service then notifies the operating system of its current state using its service status handle.

An Object-Oriented Framework

An object-oriented service framework has distinct advantages over basic Win32 functions. First of all, Win32 requires you to identify at compile time the number of services and their names. A framework lets you determine at run time the number of services to be managed. Second, using a framework, you can abstract the details of the underlying operating system to allow future porting to different operating systems. Finally, you can represent each service as a single instance of an abstract subclass.

If you think of a service as an object, then there is a natural mapping between a service's name (which is unique) and a service object. I used the Standard Template Library (STL) map template to declare a global object that represents this mapping; see Listing Three.

Since each service's ServiceMain is passed the service name as its first argument, you can register a single ServiceMain for all services and use the first argument and the name_to_service object to find the service object that corresponds to the ServiceMain call. Having a single ServiceMain function frees you from having to add or change a separate ServiceMain for each service that is added or changed.

So far, the implementation is fairly clean, but there is a problem with the Handler function. There is no additional identifying information passed to a Handler (like the service name) that you can use to resolve to which service a signal is being sent. This presents a problem, especially if you do not know until run time how many services you will be running.

To solve this problem, I first imagined what form I wanted Handler to take. I decided on the declaration in Listing Four, which let me use the servicename parameter with the name_to_service object to find the service object for which the signal is meant. The approach I finally chose was to declare a structure called NAMEANDHANDLER that contains three pointers, one of which points to a Handler. I then declared an array of NAMEANDHANDLER structures, with the first structure's handler pointer initialized to point to a function called Handler0, the second pointing to Handler1, the third pointing to Handler2, and so on. Handlern calls a single Handler, passing it members of the nth entry in the NAMEANDHANDLER array.

By including a pointer to a service object in the NAMEANDHANDLER structure that gets initialized to zero, you can obtain a Handler for a service inside ServiceMain by:

1. Going through the array of NAMEANDHANDLER structures until you find one that isn't used yet (that is, the service pointer of the structure is still NULL).

2. Obtaining the address of the Handler function from the handler pointer of the structure.

3. Registering Handler with the operating system.

4. Setting the service pointer of NAMEANDHANDLER to the service object you are registering.

5. Saving the status handle returned from the RegisterServiceCtrlHandler in the NAMEANDHANDLER structure.

The Service Interface

Each signal that the OS sends to a service (pause, continue, stop, shutdown, interrogate) can be mapped to a corresponding method in the framework's service (TryToPause, TryToContinue, TryToStop, TryToShutdown, TryToGetStatus). I use the word "try" in the names of each of these methods because the operating system's request that a service do a particular action does not guarantee that the action will be successful. When the operating system sends a signal to a service, that service must respond by calling SetServiceStatus, passing its status handle as the first parameter and the address of a SERVICE_STATUS structure as the second parameter. By setting combinations of bitfields and predefined constants in SERVICE_STATUS, a service responds to a signal from the OS by notifying the OS of any change in its state.

To abstract further, I encapsulate the interplay between the status handle, SERVICE_STATUS structure, and SetServiceStatus into a single object called TServiceController. The constructor for TServiceController is passed a status handle that it saves as a private member. The public methods exposed by TServiceController are in Listing Five.

These methods set the appropriate bitfields and integer constants in a SERVICE_STATUS structure and make the underlying call to SetServiceStatus. This effectively abstracts the communication with the operating system that the service must perform. Each TryToxxx function is passed a pointer to a TServiceController object, which it uses to notify the OS of any change in its status. I call the service object TService, and its TryToxxx public methods are in Listing Six.

Instead of saving a status handle in the NAMEANDHANDLER structure, I store a pointer to a new instance of a TServiceController function. Each Handlern calls the global Handler function with a pointer to a TServiceController object, a pointer to a TService object, and an integer representing the signal with which the Handlern was first called.

Depending on the value of the integer signal it was passed, Handler contains a switch statement that calls one of the TryToxxx methods in the TService object passing it the pointer to the TServiceController object.

Since the OS never gives a start signal to a service, but instead calls its ServiceMain to start it, I added a TryToStart function for consistency. The TryToStart returns True if the service has successfully started. It must call the ServiceStarted method of the TServiceController object before it returns True to indicate that it has started.

The complete definition of the abstract TService class is in Listing Seven.

ServiceMain Revisited

Earlier, I described how ServiceMain obtains a handler for a function. After ServiceMain obtains this handler, it does the following:

1. Uses the status handle to create a new instance of a TServiceController object.

2. Sets the servicecontroller pointer in the NAMEANDHANDLER structure to the newly created TServiceController object.

3. Calls the ParseCommandLine method of the TService object, passing it the parameters with which ServiceMain was called.

4. Calls the TryToStart method of the TService object, if TryToStart returns True, then calls the ServiceMain method of the TService object.

Because you can now hook up a service handler function with a TService object at run time, all you need to implement one or more services to an application is some way of passing a list of TService-derived objects to the framework. Since each TService- derived object implements ServiceName and DisplayName methods, the same list of TService-derived objects can be used to install or remove a list of services.

The service framework contains a reference to service_vector GetServices(int argc,char *arg[]);, where service_vector is defined as typedef vector<TService *> service_vector;. To implement one or more services in a single application, an app using this framework simply needs to implement the GetServices function. The application-supplied GetServices acts as a service factory, instantiating all of the service objects and returning them in an STL vector.

Many implementations of NT services use a second installer application that registers (or removes) the NT service app with the OS. To simplify the deployment of an NT service app, the task of registering or removing a service can be accomplished by the service application itself by the use of command-line switches. When the OS loads an executable containing one or more services, it does not pass the executable a command line; instead, each service gets its own command line when ServiceMain gets invoked. Therefore, when a service application is invoked with a command line, it can assume that it is not being invoked as a service. A service application can then use command-line switches to install or remove all of the services it contains. This removes the need for a second application that registers the service EXE with the OS.

The entry point into the service framework is defined as void smain(int argc, char * arg[]);. The smain function obtains a list of TService objects from GetServices and parses any arguments passed to it in the argument vector arg[]. If one of the arguments is "/install" (or "/remove"), it registers (or removes) each service with the OS. If no arguments are passed to smain, it assumes that it is being run as a service and proceeds accordingly.

The code for the service framework is in two files called "service.cpp" and "service.h." To create an NT service application using this framework, you simply #include "service.h" in your (console) application, write your own GetServices, and call smain with the same arguments with which main was called after doing any initialization the application may need.

Java Service Framework

Designing an equivalent object model for services in Java is relatively straightforward. I transform the TService abstract base class into a Java interface, as in Listing Eight. I call the interface the more UNIX-like JDaemon, instead of a more NT-like name. A library of objects implementing the JDaemon interface could just as easily be controlled from a Java-based control panel as the Windows NT control panel. Listing Nine is the equivalent Java interface to the TServiceController class.

Listing Ten (SampleDaemon) is an example of a simple service written in Java. SampleDaemon writes "hello" to System.out every half of a second when it is started. This code can be used as a starting point for more sophisticated Java services such as a web server or a sendmail daemon. Once all the supporting framework code is in place, writing a service or daemon in Java becomes a fairly simple task. You can concentrate on the actual tasks performed by the service and do not have to concern yourself with the OS-specific details.

To enable a Java-based service to communicate with the OS, I use JNI, which enables a Java program to communicate with natively compiled code, such as a DLL on a Windows platform, or shared library (.so) under UNIX. To declare a native method in Java, a class simply uses the native keyword in the declaration of a class method. For example, public native boolean foo(int bar); declares a native method called "foo" that takes a single integer as a parameter and returns a Boolean result. The body of the method is not defined because the method is implemented in native code.

For native methods to work correctly, the Java interpreter needs some way of knowing the address of the compiled function that corresponds to each declared native method. This can be accomplished in one of two ways:

  • A Java class uses the System.loadLibrary() call to load a shared library (.so or .dll) that contains the native methods. The Java interpreter then examines the functions exported from the shared library and resolves native methods according to the names of the exported functions. A utility called javah that comes with the JDK generates the appropriate function name for a Java native method.
  • An application that uses the Java interpreter can register native methods with the Java run time directly before invoking any Java methods.

The first technique is more common because it lets you call native methods with their code without first having to write an app that loads the Java run-time engine. However, the second technique is preferable to the first when an app must load the Java interpreter itself.

Java programs can call compiled functions using native methods, but for a compiled function to call a Java method, it must use a subset of JNI called the "Java Invocation Interface." Using the invocation interface, a compiled function can create new Java objects, invoke their methods, and inspect their members. The program java.exe is an example of a C/C++ program that uses the Invocation API to call a static method of a class whose name (as well as classpath information, and the like) is supplied on the command line.

When an application uses the Invocation API, instances of Java objects are represented as opaque handles of type jobject. To call a method of a Java object, an application must first obtain the ID of the method it wants to call. Once the method ID for an object is obtained, it can then be used to call that particular method on any instance of the object.

Tying Together C++ and Java Objects

The glue that binds the C++ service framework to the Java service framework is the Java class NTServiceController, whose declaration is in Listing Eleven.

The pointer member of the NTServiceController class is actually used as a pointer to a TServiceController C++ object. When one of NTServiceController's native methods is invoked, the native method casts the pointer member to a (TServiceController *) and calls the corresponding method in TServiceController. Listing Twelve is the implementation of the native method daemonStarting.

The C++ object that communicates with both the C++ service framework and a Java class implementing the JDaemon interface is called TJavaService, which is derived from TService so that it can communicate with the service framework. It also contains two jobject handles that point to instances of JDaemon and NTServiceController Java objects.

When the TryToStart method of the TJavaService object is called, it calls the setPointer method of the NTServiceController object, passing it the address of the TServiceController object passed to the TryToStart method. It then calls the tryToStart method of the JDaemon Java object. When the tryToStart method of the Java object implementing the JDaemon interface calls one of the methods of the NTServiceController, the native code that gets invoked can then obtain the pointer to the TServiceController and call its corresponding method. The same applies for all the other TryToxxx functions.

The JavaService Utility

The end results is the JavaService utility, which uses the NT service framework with Sun's Java Runtime Interpreter (although the code could be modified to use another run-time interpreter) and three supporting Java classes to let you write an NT service in Java and start/stop it from the Windows control panel.

To use it, you:

1. Create one or more Java objects that implement the JDaemon interface.

2. Bundle the objects together with the JDaemon, JDaemonController, and NTServiceController class files into a single JAR file called "Javaservice.jar."

3. Put the names of each fully qualified class name in separate lines in a file called "javaservice.list."

4. Place javaservice.exe, javaservice.jar, and javaservice.list in the same directory.

5. From a command prompt, install the services with: "javaservice /install."

6. To remove the services, at a command prompt type "javaservice /remove."

The names of the services should be listed under Control Panel|Services. You can then use the Start/Stop buttons in the control panel to start or stop the service(s).

As a convenience, javaservice redirects System.out to a file called javaservice.out and System.err to the javaservice.err file. This enables a simplistic form of logging for each service.

Conclusion

The javaservice program lets you write a Windows NT service entirely in Java, and control and monitor it like any other native NT service. In the course of developing the javaservice program, I developed a C++ object-oriented framework for Windows NT services that has proved to be extremely useful in its own right. In the future, I hope to mimic the functionality of NT's service control manager in Java so that you could write a service/daemon entirely in Java and control and manage that service on any OS without changing a single line of code.

DDJ

Listing One

void _stdcall ServiceMain(int argc,char **args)

Back to Article

Listing Two

void _stdcall Handler(int signal)

Back to Article

Listing Three

typedef map<string,TService *> string_service_map;
string_service_map name_to_service;

Back to Article

Listing Four

void PreferredHandler(char *servicename,int signal);

Back to Article

Listing Five

bool ServiceStarting(DWORD checkpoint,DWORD wait_hint);
bool ServiceStopping(DWORD checkpoint,DWORD wait_hint);
bool ServiceStarted();
bool ServiceStopped();
bool ServiceNothingInteresting();

Back to Article

Listing Six

virtual bool TryToStart(TServiceController *controller)=0;
virtual void TryToStop(TServiceController *controller)=0;
virtual void TryToPause(TServiceController *controller)=0;
virtual void TryToContinue(TServiceController *controller)=0;
virtual void TryToShutdown(TServiceController *controller)=0;
virtual void TryToGetStatus(TServiceController *controller)=0;

Back to Article

Listing Seven

class TService
{
public:
virtual string ServiceName()=0;
virtual string DisplayName()=0;
virtual string_vector DependentServices()=0;

// parse any command-line parameters passed to the service
virtual void ParseCommandLine(int argc,char **args)=0;
// try to start the service, returns true if service has been initialized 
// properly. If it returns true, then main will be called
virtual bool TryToStart(TServiceController *controller)=0;
// main method returns when service has stopped
virtual void ServiceMain()=0;

virtual void TryToStop(TServiceController *controller)=0;
virtual void TryToPause(TServiceController *controller)=0;
virtual void TryToContinue(TServiceController *controller)=0;
virtual void TryToShutdown(TServiceController *controller)=0;
virtual void TryToGetStatus(TServiceController *controller)=0;

virtual ~TService() {}
};

Back to Article

Listing Eight

public interface JDaemon
{
public String getDaemonName();
public String getDisplayName();
public String[] getDependentDaemons();
public void parseCommandLine(String[] args);
// returns true if we were able to start
public boolean tryToStart(JDaemonController c);
public void main(); // called when service is running
public boolean tryToStop(JDaemonController c);
public boolean tryToPause(JDaemonController c);
public boolean tryToContinue(JDaemonController c);
public boolean tryToShutdown(JDaemonController c);
public boolean tryToGetStatus(JDaemonController c);
}

Back to Article

Listing Nine

public interface JDaemonController
{
public boolean daemonStarting(int checkpoint,int wait_hint);
public boolean daemonStopping(int checkpoint,int wait_hint);
public boolean daemonStarted();
public boolean daemonStopped();
public boolean daemonNothingInteresting();
}

Back to Article

Listing Ten

public class SampleDaemon implements JDaemon
{
boolean running=false;

public SampleDaemon()
  {
  }
public String getDaemonName()
  {
    return "SampleDaemon";
  }
public String getDisplayName()
  {
    return "SimpleJavaService";
  }
public String[] getDependentDaemons()
  {
    return null;
  }
public void parseCommandLine(String[] args)
  {
  }
public boolean tryToStart(JDaemonController c)
  {
    c.daemonStarting(1,500); // starting in less than 500 milliseconds
    running=true;
    c.daemonStarted();
    return true;
  }
public void main()
  {
    int i=0;
    while(running)
      {
        try
          {
            Thread.currentThread().sleep(500);
            System.out.println("hello "+i);
            i++;
          }
        catch(InterruptedException ie)
          {
          }  
      }
  }
public boolean tryToStop(JDaemonController c)
  {
    c.daemonStopping(1,500);
    running=false;
    return c.daemonStopped();
  }
public boolean tryToPause(JDaemonController c)
  {
    return c.daemonNothingInteresting();
  }
public boolean tryToContinue(JDaemonController c)
  {
    return c.daemonNothingInteresting();
  }
public boolean tryToShutdown(JDaemonController c)
  {
    return c.daemonNothingInteresting();
  }
public boolean tryToGetStatus(JDaemonController c)
  {
    return c.daemonNothingInteresting();
  }
}

Back to Article

Listing Eleven

public class NTServiceController implements JDaemonController
{
int pointer;
public NTServiceController()
  {
    pointer=0;
  }
public void setPointer(int p) {pointer=p;}
public int getPointer() {return pointer;}

public native boolean daemonStarting(int checkpoint,int wait_hint);
public native boolean daemonStopping(int checkpoint,int wait_hint);
public native boolean daemonStarted();
public native boolean daemonStopped();
public native boolean daemonNothingInteresting();
}

Back to Article

Listing Twelve

JNIEXPORT jboolean JNICALL
    NTServiceController_daemonStarting
  (JNIEnv *env,jobject jcontroller,jint checkpoint,jint wait_hint)
{
TServiceController *sc=(TServiceController *)
    env->CallIntMethod(jcontroller,ntscm_getPointer);
return sc->ServiceStarting(checkpoint,wait_hint);
}




Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.