Writing a Windows Service in Java


I've got a couple of Java programs that I want to leave running permanently on my laptop, so I set about creating a Windows Service. I investigated several of the alternatives (Java Service Wrapper, Yet Another Java Service Wrapper, etc.), but having recently discovered Java Native Access (JNA), I decided to see if I could produce a fairly lightweight solution.

JNA (and libffi on which it depends) provides a means to dynamically create a bridge between Java and native libraries, a feature I've been wanting for years. I'd used JNI in the past, but I find it rather brittle. I've been pleasantly surprised to find that even when I made various mistakes with JNA, the JVM didn't crash; instead, JNA threw relatively meaningful exceptions, such as when it was unable to bridge the gap between a call from Windows to a Java method I wrote to accept that call (i.e. the argument types I used weren't appropriate). For example, the Windows Service API requires the service to implement a ServiceMain function which will be called by Windows:
VOID WINAPI ServiceMain(
  __in DWORD dwArgc,
  __in LPTSTR *lpszArgv
);
That lpszArgv is a pointer to an array of pointers to strings that will be passed to the ServiceMain function. I wanted to define a type, ReceiveStringArray, extending Pointer, as the type of lpszArgv in Java. Unfortunately, JNA could not bridge the gap from the native arguments to ReceiveStringArray, so I had to fallback to using JNA's Pointer as its type, and fortunately Pointer has a method, getStringArray, that handles exactly the translation needed here.
interface SERVICE_MAIN_FUNCTION extends StdCallCallback {
  /**
   * ServiceMain is the main method of the service. It should return only
   * once the service is stopped.
   * 
   * @param dwArgc
   * @param argv A pointer to an array (of length dwArgc)
   *             of pointers to strings.
   */
  void ServiceMain(int dwArgc, Pointer argv);
}
It may be that I was missing some appropriate constructor in ReceiveStringArray, or that a JNA TypeMapper was needed to handle initializing a ReceiveStringArray in this situation.

Another interesting challenge I had was that the first of my callback functions, ServiceMain, was being called at the expected time, but it was also being called when I expected a different callback function to be called instead. I'm not certain, but I think this is because JNA doesn't really care about the name of the method in the interface; there must be a single method, and that is the one that will be called. I had created two interfaces, but only a single class implementing them both. I suspected that I needed to have a separate class (or instance?) to receive each type of callback.

StartServiceCtrlDispatcher
A typical Windows Service is basically just a program that runs in the background, with no direct interaction with the user, with a few extra required interactions with Windows. When the program is started it must call StartServiceCtrlDispatcher, passing in a table of services to be started (typically just one). The dispatcher starts another thread to run ServiceMain, then it dispatches various Windows events to the service (e.g. when Windows is shutting down), and returns once the service stops.
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.win32.W32APIOptions;

public interface ExtendedAdvapi32 extends Advapi32 {
  ExtendedAdvapi32 INSTANCE = (ExtendedAdvapi32) Native.loadLibrary(
      "Advapi32", ExtendedAdvapi32.class, W32APIOptions.UNICODE_OPTIONS);
  class SERVICE_TABLE_ENTRY extends Structure {
    public String serviceName;
    public SERVICE_MAIN_FUNCTION serviceProc;
  }
  boolean StartServiceCtrlDispatcher(SERVICE_TABLE_ENTRY[] lpServiceTable);
}
The API doesn't include a length parameter to tell the dispatcher how many services are implemented by the program. Instead, the last entry in the table must have NULL pointers. Therefore, while the program doesn't need (at this point) the name of the service it is implementing, the field must not be NULL. Instead, set it to an empty string. For example:
ExtendedAdvapi32.SERVICE_TABLE_ENTRY entry =
    new ExtendedAdvapi32.SERVICE_TABLE_ENTRY();
entry.serviceName = "";
entry.serviceProc = someServiceMainFunction;
ExtendedAdvapi32.SERVICE_TABLE_ENTRY[] serviceTable =
    (SERVICE_TABLE_ENTRY[]) entry.toArray(2);
boolean result =
    ExtendedAdvapi32.INSTANCE.StartServiceCtrlDispatcher(serviceTable);

ServiceMain
The ServiceMain callback function is invoked on another thread from the main thread, and shouldn't return until the service stops (usually when Windows is shutting down, but it can also be stopped and started via a control panel). The function is passed an array of strings (as an argc and argv, just like a C program's main). The first string in the array is the name of the service. The following elements of the array are the "Start Parameters". These can be set in the service's Properties dialog box:
ServiceMain needs to call RegisterServiceCtrlHandlerEx to provide the service control dispatcher with a function to be invoked when Windows notifies the service of events (e.g. when Windows is shutting down, a user logs in or out, or there is a change in connected hardware).
public interface ExtendedAdvapi32 extends Advapi32 {
  interface HandlerEx extends StdCallCallback {
    int serviceControlHandler(int serviceControlCode, int eventType,
                              Pointer eventData, Pointer context);
  }
  class SERVICE_STATUS_HANDLE extends HANDLE {
    public SERVICE_STATUS_HANDLE() { }
    public SERVICE_STATUS_HANDLE(Pointer p) { super(p); }
  }
  SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerEx(
      String serviceName, HandlerEx handler, Object context);
}
Next, ServiceMain must call SetServiceStatus to inform Windows that the service running, and the types of event notifications the service wants to receive.
public interface ExtendedAdvapi32 extends Advapi32 {
  static final int SERVICE_WIN32_OWN_PROCESS = 0x00000010;
  class SERVICE_STATUS extends Structure {
    public int serviceType = SERVICE_WIN32_OWN_PROCESS;
    public int currentState = 0;
    public int controlsAccepted = 0;
    public int win32ExitCode = W32Errors.NO_ERROR;
    public int serviceSpecificExitCode = 0;
    public int checkPoint = 0;
    public int waitHint = 0;
  }
  boolean SetServiceStatus(SERVICE_STATUS_HANDLE serviceStatusHandle,
                           SERVICE_STATUS serviceStatus);
}
For example:
public interface ExtendedAdvapi32 extends Advapi32 {
  static final int SERVICE_RUNNING = 0x00000004;
  static final int SERVICE_ACCEPT_SHUTDOWN = 0x00000004;
  static final int SERVICE_ACCEPT_STOP = 0x00000001;
}

SERVICE_STATUS serviceStatus = new SERVICE_STATUS();
serviceStatus.currentState = ExtendedAdvapi32.SERVICE_RUNNING;
serviceStatus.controlsAccepted = (
    ExtendedAdvapi32.SERVICE_ACCEPT_STOP |
    ExtendedAdvapi32.SERVICE_ACCEPT_SHUTDOWN);
ExtendedAdvapi32.INSTANCE.SetServiceStatus(serviceStatusHandle,
                                           serviceStatus);
At this point the service can do its job, but it must also have some way to be notified that it is time to stop (e.g. via a flag shared between the ServiceMain thread and the service control handler). When ServiceMain learns that it needs to stop, it must tell Windows that it has stopped before returning.
public interface ExtendedAdvapi32 extends Advapi32 {
  static final int SERVICE_STOPPED = 0x00000001;
}

SERVICE_STATUS serviceStatus = new SERVICE_STATUS();
serviceStatus.currentState = ExtendedAdvapi32.SERVICE_STOPPED;
serviceStatus.controlsAccepted = 0; // Accept no more notifications.
ExtendedAdvapi32.INSTANCE.SetServiceStatus(serviceStatusHandle,
                                           serviceStatus);

Service Control Handler (HandlerEx)
The service control dispatcher calls the service's control handler when the requested events occur. To support just shutdown and stop events, this suffices:
public interface ExtendedAdvapi32 extends Advapi32 {
  static final int SERVICE_CONTROL_SHUTDOWN = 0x00000005;
  static final int SERVICE_CONTROL_STOP = 0x00000001;

  // Must return NO_ERROR for this, not ERROR_CALL_NOT_IMPLEMENTED.
  static final int SERVICE_CONTROL_INTERROGATE = 0x00000004;    
}

public int serviceControlHandler(int serviceControlCode, int eventType,
                                   Pointer eventData, Pointer context) {
  switch (serviceControlCode) {
  case ExtendedAdvapi32.SERVICE_CONTROL_INTERROGATE:
    return W32Errors.NO_ERROR;
  case ExtendedAdvapi32.SERVICE_CONTROL_SHUTDOWN:
  case ExtendedAdvapi32.SERVICE_CONTROL_STOP:
    // TODO Signal ServiceMain to stop.
    return W32Errors.NO_ERROR;
  default:
    return W32Errors.ERROR_CALL_NOT_IMPLEMENTED;
  }
}

Encapsulating the Windows API
To avoid polluting the 'pure' Java with all of the above, I defined the following simple interface that my services would implement (which I can use on other operating systems):
public interface ISimpleService {
  int run(String[] args);
  void stop();
}
The return value from run could (in a slightly more complicated solution) be used to set the SERVICE_STATUS.serviceSpecificExitCode field.

These two classes are used to invoke the methods of ISimpleService:
class ServiceControlHandler implements HandlerEx {
  private final ISimpleService service;
  public ServiceControlHandler(ISimpleService service) {
    this.service = service;
  }
  public int serviceControlHandler(int serviceControlCode, int eventType,
                                   Pointer eventData, Pointer context) {
    switch (serviceControlCode) {
    case ExtendedAdvapi32.SERVICE_CONTROL_INTERROGATE:
      return W32Errors.NO_ERROR;
    case ExtendedAdvapi32.SERVICE_CONTROL_STOP:
      service.stop();
      return W32Errors.NO_ERROR;
    default:
      return W32Errors.ERROR_CALL_NOT_IMPLEMENTED;
    }
  }
}

class SimpleServiceMain implements SERVICE_MAIN_FUNCTION {
  private final ISimpleService simpleService;
  private final SimpleServiceControlHandler handler;
  private SERVICE_STATUS_HANDLE serviceStatusHandle;
  public SimpleServiceMain(ISimpleService simpleService,
                           SimpleServiceControlHandler handler) {
    this.simpleService = simpleService;
    this.handler = handler;
  }
  public void ServiceMain(int argc, Pointer argv) {
    if (argc < 1 || argv == null) {
      // Missing the service name.
      return;
    }
    try {
      String[] args = argv.getStringArray(0, argc, true);
      String serviceName = args[0];
      String[] startParameters = Arrays.copyOfRange(args, 1, args.length);
      serviceStatusHandle =
          ExtendedAdvapi32.INSTANCE.RegisterServiceCtrlHandlerEx(
              serviceName, handler, null);
      setServiceStatus(ExtendedAdvapi32.SERVICE_RUNNING,
          ExtendedAdvapi32.SERVICE_ACCEPT_STOP |
          ExtendedAdvapi32.SERVICE_ACCEPT_SHUTDOWN);
      simpleService.run(startParameters);
    } finally {
      setServiceStatus(ExtendedAdvapi32.SERVICE_STOPPED, 0);
    }
  }
  private void setServiceStatus(int currentState, int controlsAccepted) {
    SERVICE_STATUS serviceStatus = new SERVICE_STATUS();
    serviceStatus.currentState = currentState;
    serviceStatus.controlsAccepted = controlsAccepted;
    ExtendedAdvapi32.INSTANCE.SetServiceStatus(
        serviceStatusHandle, serviceStatus);
  }
}
And a function to create instances and start the service control dispatcher:
public static void runSimpleService(ISimpleService service) {
  SimpleServiceControlHandler handler =
      new SimpleServiceControlHandler(service);
  SimpleServiceMain serviceMain =
      new SimpleServiceMain(service, handler);
  SERVICE_TABLE_ENTRY entry = new SERVICE_TABLE_ENTRY();
  entry.serviceName = "";
  entry.serviceProc = serviceMain;
  SERVICE_TABLE_ENTRY[] serviceTable =
      (SERVICE_TABLE_ENTRY[]) entry.toArray(2);
  ExtendedAdvapi32.INSTANCE.StartServiceCtrlDispatcher(serviceTable);
}

Example Service
Here is a trivial service that just waits to be stopped.
public class WindowsServiceHandlerExample implements ISimpleService {
  private final CountDownLatch latch = new CountDownLatch(1);
  public int run(String[] args) {
    try {
      latch.await();
    } catch (InterruptedException e) {
    }
    return 0;
  }
  public void stop() {
    latch.countDown();
  }
  public static void main(String[] args) {
    WindowsServiceUtil.runSimpleService(new WindowsServiceHandlerExample());
  }
}

Popular Posts