Creating a Windows NT/2000/XP Service
by Dean Harding

Get the sample code for this article here.

Introduction

Creating a Service under Windows NT/2000/XP isn't hard. It requires a little knowledge of how services interact with the system, but once you've got a basic framework, a service works just like any other program. The advantages a service offers over a regular program is that it will automatically start up when Windows starts up, if it crashes, you can configure Windows to automatically restart it, and you can set it to run under any account you like (for example, to restrict how a hacker can damage your system), you can also start/stop services remotely.

Remember, though, that there are also a few drawbacks to services. The first is that only NT Windows'es can use services. This means you can't run your server on a Windows 98 machine for example (though why you'd want to, I don't know). Also, services usually cannot interact with the desktop. That is, except under certain circumstances, you can't use the MessageBox function, or create windows, or anything like that.

I'll leave up it reader discretion as to whether a service best suits your needs, so let's just press ahead...

Setting Up

The first thing you need to do is create a project for your service. You can do this with any IDE you like (or you can even use make files if that's what floats your boat), but I like Visual C++. Most of the time you'll want to make a console project but a regular windows one works as well.

The very first thing you need to be able to do is install and uninstall your service from the control panel's service control manager. To see the service control manager, open the Control Panel, double-click on Administrative Tools, then on Services. I like to be able to install and uninstall my services from the command-line, with syntax like the following:

C:\> MyService.exe -install

or:

C:\> MyService.exe -uninstall

The first thing you need to do when accessing the service control manager (SCM) is open a handle to it. This is done with the OpenSCManager() function, like this:

SC_HANDLE handle = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );

Now, to install the service, we use code like this:

SC_HANDLE service = ::CreateService(
    handle,
    "MyService",
    "MyService",
    GENERIC_READ | GENERIC_EXECUTE,
    SERVICE_WIN32_OWN_PROCESS,
    SERVICE_AUTO_START,
    SERVICE_ERROR_IGNORE,
    "C:\\Path\\To\\Executable.exe",
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
);

This will create a service called "MyService" and point it to use the executable "C:\Path\To\Executable.exe".

To uninstall the service, use code like this:

// first, open a handle to the service
SC_HANDLE service = ::OpenService( handle, "MyService", DELETE );
if( service != NULL )
{
    // remove the service!
    ::DeleteService( service );
}

First, you have to open a handle to the service. We pass DELETE as the dwDesiredAccess parameter, since we want to delete the service. If that succeeds (it may fail if, for example, the service isn't installed), then we can call DeleteService.

Once you have the service installed, you'll be able to see something like this in the SCM applet:

Running the Service

Once the service has been installed, you'll then be able to start and stop it from the service control manager applet. To this, you've got to add quite a bit of functionality to your framework. The first thing you need to do is start the Service Control Dispatcher. This is responsible for responding to requests from the SCM about starting, stopping or pausing the service.

The Service Control Dispatcher can handle requests for multiple services (for example, you can have multiple copies of your program running on the same machine, each with a different name). To facilitate this, you need to setup a Dispatch Table, which maps service names to dispatch handlers.

SERVICE_TABLE_ENTRY dispatchTable[] =
{
    { "MyService", &ServiceDispatch },
    { NULL, NULL }
};

if( ::StartServiceCtrlDispatcher( dispatchTable ) == 0 )
{
    // if this fails, it's probably because someone started us from
    // the command line.  Print a message telling them the "usage"
}

Here, the ServiceDispatch function is what we'll write to respond to service control requests. So what happens when you click on Start from the SCM applet is that your program calls the StartServiceCtrlDispatcher() function, which will block the current thread waiting for service control messages from the SCM. When a message is received, it passes it to the ServiceDispatch function, which might look like this:

SERVICE_STATUS_HANDLE hStatus;
SERVICE_STATUS status;

void WINAPI ServiceDispatch( DWORD numArgs, char **args )
{
    // we have to initialize the service-specific stuff
    memset( &status, 0, sizeof(SERVICE_STATUS) );
    status.dwServiceType = SERVICE_WIN32;
    status.dwCurrentState = SERVICE_START_PENDING;
    status.dwControlsAccepted = SERVICE_ACCEPT_STOP;

    hStatus = ::RegisterServiceCtrlHandler( "MyService", &ServiceCtrlHandler );

    // more initialization stuff here

    ::SetServiceStatus( hStatus, &status );
}

Here, we call RegisterServiceCtrlHandler to set the callback for actually responding to service control messages. You should replace the comment with the rest of your initialization code. You'll want to create another thread to do the actual work of your service. When the ServiceDistpatch exits, the thread will block waiting for more service control requests.

The ServiceCtrlHandler is another function that we write which actually responds to these service control requests. A basic version might look like this:

void WINAPI ServiceCtrlHandler( DWORD control )
{
    switch( control )
    {
    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
        // do shutdown stuff here

        status.dwCurrentState = SERVICE_STOPPED;
        status.dwWin32ExitCode = 0;
        status.dwCheckPoint = 0;
        status.dwWaitHint = 0;
        break;
    case SERVICE_CONTROL_INTERROGATE:
        // just set the current state to whatever it is...
        break;
    }

    ::SetServiceStatus( hStatus, &status );
}

The important thing here is that when you get the SERVICE_CONTROL_SHUTDOWN or SERVICE_CONTROL_STOP control message, you should stop all your threads and shutdown the service. Also of note, if you get a SERVICE_CONTROL_SHUTDOWN message, that means that Windows is shutting down. You only get about 2 seconds to stop processing before Windows will kill your process, so you might need to do something different to make the shutdown happen faster.

Debugging a Service

This is where things get tricky. You cannot debug a service that is not set to interact with the desktop, also, because of the way services work, you need to be able to attach your debugger to a running service.

To get over the first problem, you need to have the service run under the LocalSystem account and be able to "Interact with Desktop". To do this, right-click on your service, and go to Properties, go to the Log On tab, and check "Allow service to interact with desktop", as in the screen shot below:

Next, to be able to actually debug the service, you've got to get your debugger to attach to the running process. This can be done two ways. When the service has started, you can right-click on the process name in the Task Manager and select Debug from the menu. This will start up your debugger and you can then set break-points and such to your heart's content.

Another method is to insert a call to DebugBreak in you code somewhere. Then, when it get's there, it'll raise an exception and Windows will let you attach the debugger. You'll need to step out of the DebugBreak function before you'll be able to see any source code, though...

End Game

And that's pretty much all you need to know! The example framework that I've included add quite a bit of functionality to what I've described here. For example, you can start and stop the service from the command-line, and you can run the service and a regular windows console application. This is good because it makes debugging easier.

If you have any comments or questions, don't hesitate to mail me: dean@codeka.com.


Codeka.com - Just click it.

Discuss this article in the forums


Date this article was posted to GameDev.net: 3/20/2003
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Sweet Snippets
Windows

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!