An Introduction to NT Services

1. Services - What are they and why would you want to write one?

1.1 What is an NT Service?

An NT Service is a background process which is loaded by the Service Control Manager of the NT kernel. They are often loaded at bootup, before any user logs in, and are often independent of any specific user being logged on at the time. If a service is not launched automatically by the system at boot time, as many services are, it can also be manually launched by a user at the console, via the NT Control Panel's Services applet, or by another program which interfaces to NT's Service Control Manager.

1.2 Why do we need Services?

In many cases, the operating system needs a way to run background processes independently of who, if anyone, is using the console. UNIX uses daemons for this, DOS uses TSRs run from AUTOEXEC.BAT or CONFIG.SYS, etc. NT, desiring an improvement upon the somewhat unreliable methods used by 16 bit DOS (DOS TSRs seem to have a nasty habit of stepping on each other), uses Services. So, why do we need a reliable method for the OS to run background processes?

Some normal processes which are naturally implemented as services are:

Basically, any task that you want to run in the background, independently of the machine's console, is a good candidate for implementation as a service. This is not to say that services must be totally independent of the console. They can have user interfaces. They can also be manually started and shut down by the user when desired. Usually however, services are background applications which run all the time, and are thus activated automatically by NT at system bootup, and shut down only at system shutdown.


2. How services are built

Services are normal Windows 32 bit executables, and can be either GUI mode applications (with a WinMain() function) or console mode (with a main() function). In either case, the main function of a service should do nothing but register the service's ServiceMain() (this name is used by convention, but not necessarily in your code) function, by calling StartServiceCtrlDispatcher(), and then return. NT will then call the registered ServiceMain() when it is time to start the service.

When it is called by the Service Control Manager, the ServiceMain() function should call the API function RegisterServiceCtrlHandler() to register a Handler() function with the Service Control Manager, for it to call with service control requests. Like ServiceMain(), your Handler() function can be called anything, but is documented in the Win32 API documentation as Handler().

ServiceMain() should then start the thread which actually performs the service's functions. After this, ServiceMain() can not return until the service is stopped. When it is time to restart the service, the Service Control Manager will simply call ServiceMain() again.

The Handler() function is passed Control requests from the Service Control Manager. The following are the standard set of requests used by the SCM to control the service:

Service-specific user requests can also be passed through the SCM to the service (see discussion below), and any desired parameters may be placed in the service's area of the system registry for storage. Each service may create subkeys or values in the designated key:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<ServiceName>

where servicename is the Service Control Manager name for the service. (This will be discussed below.)


3. The Sample Applications

3.1 ServiceApp - the basic service application.

I have constructed just about the most basic sample service application possible. The sample service application is a console mode application, which does nothing but beep at a specified time interval.

To begin, let's look at the main() function. All this function does is register the service with the Service Control Manager, and return. To register the service, the function StartServiceCtrlDispatcher() is called, and its only parameter is a pointer to an array of SERVICE_TABLE_ENTRY structures. You may place more than one service in an executable, and if there were more than one service in this module, we would register all of them here by placing them in the serviceTable array.

// The name of the service
char *SERVICE_NAME = "BeeperService";
.
. <code snipped from app here>
.
VOID main(VOID)
{
   SERVICE_TABLE_ENTRY serviceTable[] =
   {
   {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
   { NULL, NULL }
   };
   BOOL success;
   // Register the service with the Service Control Manager
   success = StartServiceCtrlDispatcher(serviceTable);
   if (!success)
   {
      // This error message would not show up unless we had the "allow service to
      // interact with desktop" option checked, which is unlikely for a console mode
      // app, but we'll print an error message anyway just in case there's someone
      // around to see it.
      HandleError("StartServiceCtrlDispatcher fails!", GetLastError());
   }
}

The SERVICE_TABLE_ENTRY used by StartServiceCtrlDispatcher() has two fields. The first is the internal name used by the Service Control Manager to refer to the service. This must be unique on the system, so no two services you create should have the same name, nor should you use the name of any other service you have seen. Perhaps Microsoft could start a registry of service names to prevent conflicts, but I doubt this will happen, so when you write your own services, try to make sure this is unique, and very unlikely to be chosen by someone else. Note that this name is not the same name used to refer to the service in the Control Panel Services applet. This name is used only by the SCM, and by your applications when referring to the service through the SCM.

The second field of the SERVICE_TABLE_ENTRY is the address of the service's ServiceMain() function. Note that if you had two services in an executable, they could not both be named ServiceMain(), but should obviously still be named so that we know what the purpose of each function is. If multiple services are in a module, I like to name the functions for each starting with the SCM name of the service, and follow it with the function name in the API documentation, since this informs us for each function of the function purpose and the service to which it applies. So our for example ServiceMain() would be renamed BeeperService_ServiceMain().

Now lets have a look at the ServiceMain function. The first thing it must do is call the API function RegisterServiceCtrlHandler(). This registers the service's Handler() function to handle SCM requests. It then calls our function UpdateSCMStatus() to let the SCM know that it is making progress in starting the service. UpdateSCMStatus() is a shell function we've created around the API function SetServiceStatus(), which takes two parameters. The first parameter is the handle to the service, which is returned by RegisterServiceCtrlHandler(), and is assigned in our example into the global variable serviceStatusHandle. The second parameter is a SERVICE_STATUS structure. We use UpdateSCMStatus() to fill in the contents of the structure and then call SetServiceStatus() because a significant amount of the assignment which takes place to the structure each time is repetitive. UpdateSCMStatus() will be illustrated and discussed below. The only thing we need to know now is that each time we call UpdateSCMStatus(), we are letting the service know where we are in the process of starting.

//   ServiceMain -
//   ServiceMain is called when the Service Control Manager wants to
// launch the service.  It should not return until the service has
// stopped. To accomplish this, for this example, it waits blocking
// on an event just before the end of the function.  That event gets
// set by the function which terminates the service above.  Also, if
// there is an error starting the service, ServiceMain returns immediately
// without launching the main service thread, terminating the service.
VOID ServiceMain(DWORD argc, LPTSTR *argv)
{
   BOOL success;
   // First we must call the Registration function
   serviceStatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME,
                           (LPHANDLER_FUNCTION) ServiceCtrlHandler);
   if (!serviceStatusHandle)
   {
      terminateService(GetLastError());
      return;
   }
   // Next Notify the Service Control Manager of progress
   success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 5000);
   if (!success)
   {
      terminateService(GetLastError());
      return;
   }
   // Now create the our service termination event to block on
   killServiceEvent = CreateEvent (0, TRUE, FALSE, 0);
   if (!killServiceEvent)
   {
      terminateService(GetLastError());
      return;
   }
   // Notify the SCM of progress again
   success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000);
   if (!success)
   {
      terminateService(GetLastError());
      return;
   }
   // Check for a beep delay parameter passed in...
   if (argc == 2)
   {
      int temp = atoi(argv[1]);
      if (temp < 1000)
      {
         beepDelay = 2000;
      }
      else
      {
         beepDelay = temp;
      }
   }
   // Notify the SCM of progress again...
   success = UpdateSCMStatus(SERVICE_START_PENDING, NO_ERROR, 0, 3, 5000);
   if (!success)
   {
      terminateService(GetLastError());
      return;
   }
   // Start the service execution thread by calling our StartServiceThread function...
   success = StartServiceThread();
   if (!success)
   {
      terminateService(GetLastError());
      return;
   }
   // The service is now running.  Notify the SCM of this fact.
   serviceCurrentStatus = SERVICE_RUNNING;
   success = UpdateSCMStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
   if (!success)
   {
      terminateService(GetLastError());
      return;
   }
   // Now just wait for our killed service signal, and then exit, which
   // terminates the service!
   WaitForSingleObject (killServiceEvent, INFINITE);
   terminateService(0);
}

After registering our control request handler and updating the status, we create an NT event and place it in a global variable, killServiceEvent. We do this because ServiceMain is not to return until the service is stopped, and we can then later block the thread waiting for that event, and set the event to end the service.

After creating the event, we update the status one more time, and then check for a parameter to be passed in for our delay between beeps. If one is not passed in, we use a default of two seconds (most NT time specifications are in milliseconds, as this one is). We then update the service status, and proceed to launch the thread's primary thread of execution. Our app's simple function StartServiceThread() does this very simply by calling CreateThread() with the address of the ServiceExecutionThread() function, which just beeps in an endless loop.

// ServiceExecutionThread -
//   This is the main thread of execution for the
// service while it is running.
DWORD ServiceExecutionThread(LPDWORD param)
{
   while (serviceRunning)
   {
      Beep(200,200);
      Sleep(beepDelay);
   }
   return 0;
}
// StartService -
//   This starts the service by creating its execution thread.
BOOL StartServiceThread()
{
   DWORD id;
   // Start the service's thread
   threadHandle = CreateThread(0, 0,
                                                   (LPTHREAD_START_ROUTINE) ServiceExecutionThread,
                                                   0, 0, &id);
   if (threadHandle == 0)
   {
      return FALSE;
   }
   else
   {
      serviceRunning = TRUE;
      return TRUE;
   }
}

Lastly, now that ServiceMain() has launched the service thread it updates the SCM status one last time to running, as well as the application's own copy of it's current status, and then we let this thread block on the killThreadEvent by calling the Win32 API function WaitForSingleObject(). Finally, when WaitForSingleObject() returns, the killServiceEvent will have been set, so the service is stopping, and we call our function terminateService, which cleans up.

Now we'll stop to discuss UpdateSCMStatus() for a moment. This function simple fills in the fields of the SERVICE_STATUS structure before calling the API SetServiceStatus() function. However, filling in these fields is repetitive, making the function worthwhile. The first field of the SERVICE_STATUS structure to be filled in is the dwServiceType field. We use SERVICE_WIN32_OWN_PROCESS here because this is not a driver (SERVICE_KERNEL_DRIVE) or file system driver (SERVICE_FILE_SYSTEM_DRIVER), and there is only one service in this module. If there were two or more services in this executable, we would use SERVICE_WIN32_SHARE_PROCESS to indicate that two or more services were running in this process space. Also, if the service had a user interface, we would or whatever other constant we were using with SERVICE_INTERACTIVE_PROCESS.

Next we plug whatever status constant the function is passed in to the dwCurrentState. These constants are used to let the SCM know what the status of the service is, and this is in some circumstances visible to the user in the Service Control Panel. The constants let the SCM know the service is running, paused, or stopped, or that one of these states is pending.

In the dwControlsAccepted field we tell the Service Manager what Service Control Requests the service will process. The standard SCM requests are to stop, pause, continue, shutdown (sent in the event of a system shutdown) and return status. The only request a service absolutely must process is interrogation, the request for service status. All the others are optional. For our sample service, we will process all requests, so we bitwise-or together the various request acceptance constants, unless our status is start pending, in which case we do not want to be processing any other requests until the service is started, so we set dwControlsAccepted to 0.

The two exit code fields dwWin32ExitCode and dwServiceSpecificExitCode are used to return error status to the SCM in the event of an error in the service. If a Win32 API error occurs, the service should return the error value in dwWin32ExitCode and this can be logged in the system event log, otherwise if the service has its own documented set of error codes, it can return one of these in dwServiceSpecificExitCode and this will go in the log.

The dwCheckPoint field is used to let the SCM know that the service is making progress in some lengthy course of action. For example, in our ServiceMain() function above, note that we start passing dwCheckPoint at 1 with the SERVICE_START_PENDING status, and keep incrementing it through each call to the function until our status is running, when we set status to SERVICE_RUNNING and reset dwCheckPoint to 0. We carry on this process of updating the dwCheckPoint to let the SCM know that the service is still in the process of starting up, but not frozen or in some other error state.

The last field of the SERVICE_STATUS structure is dwWaitHint. This tells the SCM how long it should expect to wait between status updates for pending operations. A status update consists of either the dwCheckPoint being updated or the dwCurrentState being updated. We set dwWaitHint to an arbitrary 5 seconds, which is far longer that it should take any of these functions to run on most any system capable of running NT with reasonable performance. However, you may want to set this value to something greater on slow systems, or for larger applications. If the amount of time specified here passes without an update to dwCurrentState or dwCheckPoint being passed with another call to SetServiceStatus, the SCM will assume an error has occurred in the Service.

// This function updates the service status for the SCM
BOOL UpdateSCMStatus (DWORD dwCurrentState,
                      DWORD dwWin32ExitCode,
                      DWORD dwServiceSpecificExitCode,
                      DWORD dwCheckPoint,
                      DWORD dwWaitHint)
{
   BOOL success;
   SERVICE_STATUS serviceStatus;
   // Fill in all of the SERVICE_STATUS fields
   serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
   serviceStatus.dwCurrentState = dwCurrentState;
   // If in the process of something, then accept
   // no control events, else accept anything
   if (dwCurrentState == SERVICE_START_PENDING)
   {
      serviceStatus.dwControlsAccepted = 0;
   }
   else
   {
      serviceStatus.dwControlsAccepted =
         SERVICE_ACCEPT_STOP |
         SERVICE_ACCEPT_PAUSE_CONTINUE |
         SERVICE_ACCEPT_SHUTDOWN;
   }
   // if a specific exit code is defines, set up
   // the Win32 exit code properly
   if (dwServiceSpecificExitCode == 0)
   {
      serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
   }
   else
   {
      serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
   }
   serviceStatus.dwServiceSpecificExitCode =   dwServiceSpecificExitCode;
   serviceStatus.dwCheckPoint = dwCheckPoint;
   serviceStatus.dwWaitHint = dwWaitHint;
   // Pass the status record to the SCM
   success = SetServiceStatus (serviceStatusHandle, &serviceStatus);
   if (!success)
   {
      KillService();
   }
   return success;
}

One other important thing to note is that if your service goes too long between status updates (i.e. if the interval specified in dwWaitHint is exceeded), the SCM may think your service has stopped when it in fact has not. If this happens, your service may be running, but the SCM will not allow you to shut it down with the Services Control Panel, because it considers the service to be stopped or failed. So, the dwWaitHint is important, to make sure that the SCM does not jump the gun in making the assumption your service has failed to respond. If this happens, the SCM can get in a, shall we say, somewhat ill state, and you will probably want to be rebooting your machine in a quick and orderly fashion.

The last part of the program to discuss is the Handler() function, which I have called ServiceCtrlHandler for clarity. (I find Handler() too generic.) This function is called by the Service Control Manager with a single parameter, a control code, to request a specific action on the part of the service.

//   Handles the events dispatched by the Service Control Manager.
VOID ServiceCtrlHandler (DWORD controlCode)
{
BOOL success;
   switch(controlCode)
   {
      // There is no START option because
      // ServiceMain gets called on a start
      // Pause the service
      case SERVICE_CONTROL_PAUSE:
         if (serviceRunning && !servicePaused)
         {
            // Tell the SCM we're about to Pause.
            success = UpdateSCMStatus(SERVICE_PAUSE_PENDING, NO_ERROR,
                                      0, 1, 1000);
            servicePaused = TRUE;
            SuspendThread(threadHandle);
            serviceCurrentStatus = SERVICE_PAUSED;
         }
         break;
      // Resume from a pause
      case SERVICE_CONTROL_CONTINUE:
         if (serviceRunning && servicePaused)
         {
            // Tell the SCM we're about to Resume.
            success = UpdateSCMStatus(SERVICE_CONTINUE_PENDING, NO_ERROR, 0,
                                      1, 1000);
            servicePaused=FALSE;
            ResumeThread(threadHandle);
            serviceCurrentStatus = SERVICE_RUNNING;
         }
         break;
      // Beep now in response to our special control message.
      case JW_SERVICE_CONTROL_BEEP_NOW:
         Beep(500, 500);
         break;
      // Update the current status for the SCM.
      case SERVICE_CONTROL_INTERROGATE:
         // This does nothing, here we will just fall through to the end
         // and send our current status.
         break;
      // For a shutdown, we can do cleanup but it must take place quickly
      // because the system will go down out from under us.
      // For this app we have time to stop here, which I do by just falling
      // through to the stop message.
      case SERVICE_CONTROL_SHUTDOWN:
      // Stop the service
      case SERVICE_CONTROL_STOP:
         // Tell the SCM we're about to Stop.
         serviceCurrentStatus = SERVICE_STOP_PENDING;
         success = UpdateSCMStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
         KillService();
         return;
      default:
          break;
   }
   UpdateSCMStatus(serviceCurrentStatus, NO_ERROR, 0, 0, 0);
}

To handle the Pause request, first we call UpdateSCMStatus() to change the SCM service status to indicate that a pause is pending, then pause the execution thread using the Win32 API PauseThread() function, then set our own flag to indicate we're paused, and then finally set the application's serviceCurrentStatus variable to paused and let the code fall out of the case statement, and call UpdateSCMStatus which will then update the SCM to say that the service is paused.

We handle the Resume request in the same way, except that we reset the pause flag to false instead of true, update the SCM to indicate that the service is executing again, and rather than pausing the thread we resume its execution.

The service may also process user specific control messages between 128 and 255. I have specified my own message, JW_SERVICE_CONTROL_BEEP_NOW to beep at a lower tone, for a longer time, than the normal beep. An application which sends this message will be demonstrated below. To handle this message, the service just beeps, sets the status to running for the fall-through, and updates the SCM that everything is still fine.

If a status interrogation request is received, the case statement just falls out and updates the SCM with the last known value of the application's status, as stored in the serviceCurrentStatus variable.

If a Shutdown or Stop message is received, UpdateSCMStatus() is called to tell the SCM a stop is pending, and then a call to our function KillService() is made. KillService() just sets the global serviceRunning flag to false, then sets the killServiceEvent, which will allow the ServiceMain to return, indicating to the SCM that the service has stopped.// KillService -// Set the service running flag off and set

// the killServiceEvent to allow ServiceMain to exit.
void KillService()
{
  serviceRunning=FALSE;
  // Set the event that is blocking ServiceMain
  // so that ServiceMain can return
  SetEvent(killServiceEvent);
}

3.2 Installing the BeeperService application

To install the BeeperService application, you must open a connection to the Service Control Manager using the API function OpenSCManager(), and then use the returned handle to call CreateService to create a new entry in the Service Control Manager database. Note the different names for internal use by the SCM and display by the Service Control Panel (parameters two and three to the CreateService call, respectively). Finally, the service installation application must close the handles opened to the service and the SCM.

The following code illustrates this:

void main(int argc, char *argv[])
{
   SC_HANDLE myService, scm;
   cout << "Installing Beeper Service...\n";
   // open a connection to the SCM
   scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE);
   if (!scm)
   {
      HandleError("OpenSCManager fails!", GetLastError());
   }
   cout << "Opened Service Control Manager...\n";
   // Install the new service
   myService = CreateService(
      scm, "BeeperService", // the internal service name used by the SCM
      "My Beeper Service",  // the external label seen in the Service Control applet
      SERVICE_ALL_ACCESS,  // We want full access to control the service
      SERVICE_WIN32_OWN_PROCESS,  // The service is a single app and not a driver
      SERVICE_DEMAND_START,  // The service will be started by us manually
      SERVICE_ERROR_NORMAL,  // If error during service start, don't misbehave.
      "D:\\WORK\\Conference\\Services\\ServiceApp\\ServiceApp.exe",
      0, 0, 0, 0, 0);
   if (!myService)
   {
      HandleError("CreateService fails!", GetLastError());
   }
   else
   {
      cout << "Service successfully installed.\n";
   }
   // clean up
   CloseServiceHandle(myService);
   CloseServiceHandle(scm);
}

3.2 Removing the BeeperService application

To remove the BeeperService application, as with installing the service, you must first open a connection to the Service Control Manager using the API function OpenSCManager(). You then use the returned SCM handle to open a handle to the BeeperService using OpenService(). You then use the handle returned by OpenService() to call DeleteService() to delete the entry in the Service Control Manager database. In this example, we check to see if the service is running first, using the QueryServiceStatus() function, and if it is running, we use ControlService() to stop the service before attempting to delete it from the Service Control Manager database.

The following code illustrates this:

void main(int argc, char *argv[])
{
   SC_HANDLE myService, scm;
   BOOL success;
   SERVICE_STATUS status;
   cout << "Removing Service...\n";
   // Open a Service Control Manager connection
   scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE);
   if (!scm)
   {
      HandleError("OpenSCManager Fails!", GetLastError());
   }
   cout << "Opened Service Control Manager...\n";
   // Get the service's handle
   myService = OpenService(scm, SERVICE_NAME, SERVICE_ALL_ACCESS | DELETE);
   if (!myService)
   {
      HandleError("OpenService Fails!", GetLastError());
   }
   // Stop the service if necessary
   success = QueryServiceStatus(myService, &status);
   if (!success)
   {
      HandleError("QueryServiceStatus fails!", GetLastError());
   }
   if (status.dwCurrentState != SERVICE_STOPPED)
   {
      cout << "Service currently active.  Stopping service...\n";
      success = ControlService(myService, SERVICE_CONTROL_STOP, &status);
      if (!success)
      {
         HandleError("ControlService fails to stop service!", GetLastError());
      }
      Sleep(500);
   }
   // Remove the service
   success = DeleteService(myService);
   if (success)
   {
      cout << "Service successfully removed.\n";
   }
   else
   {
      HandleError("DeleteService Fails!", GetLastError());
   }
   CloseServiceHandle(myService);
   CloseServiceHandle(scm);
}

3.3 Sending our own message to the BeeperService application

As discussed above (though this seems a long time ago by now!) we can also send our own user specific messages to a service. The user specific messages must be constants between 128 and 255. Here is a demonstration that will send our custom beep message to the BeeperService. It opens a connection to the Service Control Manager, then opens a handle to the service using OpenService(), just like the RemoveService application. It then sends the custom message using ControlService(), which is the same function used above to stop the service before we wanted to remove it.

void main(int argc, const char *argv[])
{
   SC_HANDLE myService, scm;
   BOOL success;
   SERVICE_STATUS status;
   // Open a connection to the SCM
   scm = OpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS | GENERIC_WRITE);
   if (!scm)
   {
      HandleError("OpenScManager Fails!", GetLastError());
   }
   // Get the service's handle
   myService = OpenService(scm, SERVICE_NAME, SERVICE_ALL_ACCESS);
   if (!myService)
   {
      HandleError("OpenService Fails!", GetLastError());
   }
   // Send the service the desired control message.
   success = ControlService(myService, JW_SERVICE_CONTROL_BEEP_NOW, &status);
   if (!success)
   {
      HandleError("ControlService Fails!", GetLastError());
   }
   cout << "Service control message successfully sent!" << endl;

   // Clean up
   CloseServiceHandle(myService);
   CloseServiceHandle(scm);
}


4. Debugging Services and Advanced Notes

Debugging services, especially those without a UI, can be difficult.

One option is to simply allow your service access to the desktop during development, and print debug messages, and then turn off desktop access for the final product.

My own personal experience is that the best way to develop a service is to build the service's thread of execution into a separate C++ module than the service code itself (a TThread descendant in its own module is my preference), and then to link the execution thread with a non-service shell application, test it running that way, using the debugger if needed, and then move the thread into the service app.

The thing to watch out for in the move from a regular app to a service application, is that the context of the application has changed from that of a normal user mode process. (Note that services are still user mode processes, not kernel mode, unless they are specified as drivers.)

In making an application a service, things like your default security context and WinStation may have changed. For example, open a named pipe server with no security attributes in a user mode process, and another user mode process on the same machine will have client access to the pipe. But open the same server pipe from your service, and try accessing the pipe from the same client. It will fail, unless your service is set to run logged in as the same user the particular client is logged in as. Worse yet, try calling EnumWindows from a service application, with something in particular loaded on the desktop. Notice that whatever app you are looking for will not show up in the window list returned, even if the service is set to run logged in as the same user as the one using the console. That is because the service does not by default have access to the same desktop as the applications running on the console.

This is too big a topic to address here in an introductory paper, as the NT process and User Interface models have more than enough complexity for papers of their own, but let it suffice to say that the switch from an application to a service is not entirely insignificant. If you plan for your service to have significant interaction with user mode apps, you will probably want to establish some kind of client for the service which runs on the desktop, and communicates with the service through a named pipe or socket.

Also, if any NT objects created in your service (named pipes for example) will be accessed from outside your service, they will probably need to have explicitly set (from the API) security settings to allow access to them from the context of other processes.

Also, this may seem like obvious advice, but people seem to need it - if you have more than one service in a module, try to only debug one at a time if possible!


5. Other Tidbits

If StartServiceCtrlDispatcher fails, an error should be logged, using the NT system event log. If the service is allowed user interaction, a dialog may be placed onscreen to notify the user of the error, but if you do this, be sure that the dialog does not interfere with system startup in any other way (ie preventing the user from logging in, or preventing other apps from running in some way).

Also remember that if you start a console mode service with "Allow service to interact with the desktop", an empty console window will automatically be opened for the service when it starts, so you have to build your service as a GUI mode application if you plan for your service to have any user interaction.


6. Where to find other documentation

6.1 Other reference materials

The following is a list of other reference materials I have used to learn about services and the development of service applications.

Win32 System Services, by Marshal Brian, published by Prentice Hall 1996. This well written book contains the most in-depth treatment I have seen to date of service development, as well information on NT security, RPC servers, networking, and other not well-understood NT related topics. A definite two thumbs up for anyone looking to know more about NT development.

The following articles, related source code, and Win32 documentation excerpts can all be found on the current MSDN Level 1 CD-ROM:

"Creating a Simple Windows NT Service in C++", by Nigel Thompson. This articles includes source code for and describes a C++ object implementing a service. You could modify or inherit from this object to create your own.

Win32 SDK Documentation, Overviews, Window Management, System Services, Services. The Win32 SDK overview of writing a service with code samples.

Win32 SDK Documentation, Overviews, Window Management, System Services, Window Stations and Desktops. The Win32 SDK overview of Window Stations and Desktops under NT. This is crucial information for writing interactive services, since services do not by default have access to the Application Desktop, or to the same Window Station accessed by programs the logged on user runs from the console. This chapter specifically talks about how to access the user desktop from a service.

Windows NT 3.5 SDK - Win32 Programmer's Reference, Win32 Service Processes. A smaller (3 printed pages, no code) overview of the functions to write to implement a service.

Windows NT 3.5 SDK - Win32 Programmer's Reference, Using Services. The more thorough SDK overview of writing a service, with code examples.

"Windows NT Security" by Christopher Nefcy. An overview of the NT Security system with code samples. You will find knowledge of NT security useful for writing services because you will probably need to implement some security code to allow access from other processes to named pipes or other NT objects owned by your service.

"Windows NT Security in Theory and Practice" by Ruediger R. Asche. A brief (no code, 11 printed pages) discussion of NT Security. The first in a series of three articles, the next two noted below.

"The Guts of Security" - Part II of the Security article series by Ruediger R. Asche. A thorough overview of NT Security with code samples, including C++ classes for handling security implementation.

"Security Bits and Pieces" - Part III of the Security article series by Ruediger R. Asche. A short discussion of the architecture of the sample applications for the series of Security articles.

6.2 Service Related Win32 API Functions

The following list of NT Service related functions is taken straight from the Win32 API Reference. If you simply read the documentation on each service related function, hopefully after browsing this article, you will find you pick up a lot quickly.

ChangeServiceConfig

CloseServiceHandle

ControlService

CreateService

DeleteService

EnumDependentServices

EnumServicesStatus

GetServiceDisplayName

GetServiceKeyName

Handler

LockServiceDatabase

NotifyBootConfigStatus

OpenSCManager

OpenService

QueryServiceConfig

QueryServiceLockStatus

QueryServiceObjectSecurity

QueryServiceStatus

RegisterServiceCtrlHandler

ServiceMain

SetServiceBits

SetServiceObjectSecurity

SetServiceStatus

StartService

StartServiceCtrlDispatcher