New Features of Service Applications for Microsoft Windows 2000

Donis Marshall
Gearhead Press

August 1999

Summary: In Microsoft® Windows® 2000, enhancements to the service model represent an important step in the evolution of services. This article details many exciting new features with several code examples that can be applied to your service. (9 printed pages)

Introduction

In Microsoft® Windows® 2000, enhancements to the service model represent an important step in the evolution of services. Many exciting new features include:

This article details these features with several code examples that can be applied to your service.

Application Development Refined

Windows 2000 is the harbinger of innovative new technologies and represents a refinement in the science of developing Windows applications. The methodology of writing a service application remains intact. However, specific changes in the Platform SDK as related to services are compelling and merit examination. These changes affect the three primary components of the service architecture: service applications, service control programs (SCPs), and the service control manager (SCM).

This article focuses on changes with Microsoft Win32® services. Device drivers and net services are not considered.

In the Win32 environment, services are background processes that persist across logon sessions. Services usually do not present a graphical user interface (GUI), and are managed by the service control manager. The SCM, which is a remote procedure call (RPC) server, supports local or remote management of a service. You can construct applications that indirectly control services through the SCM with service functions exported from advapi32.dll. This type of application is called an SCP. Typically, a user administers a service through an SCP. There are both generic and product-specific SCPs.

Although a service application is a functional Win32 application, it should be started solely by the SCM and never by the user. The SCM maintains a list of services and collateral data in the registry. This information is essential for launching a service. Auto-start services are started when the operating system boots. Demand-start services are started on demand by users through an SCP. The services database resides in the registry at HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services.

Services are not supported in Windows 98 or Windows 95.

New Features of Service Applications

A service executable can host one or more services—each of which is spawned in a separate thread. Then, a service designates a handler to catch service-control messages sent by the SCM. For this reason, incoming service-control messages are routed to the handler by the service-control dispatcher, which runs as a thread inside of the service executable. A handler will respond to the message.

The Service controller maintains a count of the running services in each service executable. When the count drops to zero, it tells the service control dispatcher to exit, and the service application closes shortly thereafter. The dispatcher and any handlers execute on the same thread. However, services and representative handlers run on different threads.

Once a service is started, the service is given 82 seconds to register a handler and call SetServiceStatus for the first time. In Windows 2000, a service designates a handler with the RegisterServiceCtrlHandlerEx function.

SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerEx(LPCTSTR lpServiceName,
LPHANDLER_FUNCTION_EX lpHandlerProc,   LPVOID lpContext )

This function extends RegisterServiceCtrlHandler and has a single additional parameter—lpContext. The last parameter is 32 bits of programmer-defined data. It is useful when services share the same handler. Within the handler, lpContext can help identify the service context. In the following scenario, lpContext is an integral value naming a service [Code 1].

Code 1

// In ServiceA
RegisterServiceCtrlHandler("ServiceA", HandlerEx, (LPVOID) 1);
…

// In ServiceB
RegisterServiceCtrlHandler("ServiceB", HandlerEx, (LPVOID) 2);
…

// In Handler
DWORD WINAPI HandlerEx(  DWORD dwControl,  DWORD dwEventType, 
     LPVOID lpEventData, LPVOID lpContext)
{
   if( ((int) lpContext) == 1 )
   {
      // ServiceA specific code
   }
   else
   {
      // ServiceB specific code
   }
}
      

In addition, lpContext can be a pointer to data shared between a service and its companion handler.

With RegisterServiceCtrlHandlerEx, the second parameter HandlerEx replaces Handler.

DWORD WINAPI HandlerEx(  DWORD dwControl,  DWORD dwEventType, 
     LPVOID lpEventData, LPVOID lpContext)

The first parameter is identical to the corresponding parameter of Handler. It is the service control message forwarded by the service control dispatcher.

The dwEventType, lpEventData, and lpContext parameters are new to Windows 2000. In addition, the return type has changed. The dwEventType and lpEventData parameters are applicable to a subset of service control messages that track changes in the state of system hardware or configuration. The dwEventType and 1pEventData parameters are not relevant for the other service control messages.

The lpContext member is the optional data transmitted from RegisterServiceCtrlHandlerEx directly to the handler. The new handler returns an error code—not void. If the service control message is handled, NO_ERROR is returned. Otherwise, ERROR_CALL_NOT_IMPLEMENTED is returned. If the message is related to the hardware state or status, returning a Win32 error code other than NO_ERROR will reject the event.

In Windows 2000, both Handler and HandlerEx can receive the new service control message that notifies a service of changes to its parameters (stored at HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Services \ service name \ Parameters). If relevant, the service should reread its parameters.

SERVICE_CONTROL_PARAMCHANGE

In addition, HandlerEx gets extra messages unique to itself. These messages help a service react appropriately to changes in the hardware configuration or the state of the machine. These service control messages are:

   SERVICE_CONTROL_HARDWAREPROFILECHANGE
   SERVICE_CONTROL_DEVICEEVENT      
   SERVICE_CONTROL_POWEREVENT

To accept these new messages, the appropriate flags must be submitted using SetServiceStatus. In the SERVICE_STATUS structure, the dwControlsAccepted member should be updated to reflect the desired messages. The bitwise flags are:

   SERVICE_ACCEPT_PARAMCHANGE
   SERVICE_ACCEPT_HARDWAREPROFILECHANGE
   SERVICE_ACCEPT_POWEREVENT

In addition, a service registers for SERVICE_CONTROL_DEVICEEVENT notifications by calling the RegisterDeviceNotification function.

With the exception of SERVICE_CONTROL_PARAMCHANGE, an SCP cannot send these new messages with the function ControlService.

When the hardware profile of a system is altered, a SERVICE_CONTROL_HARDWAREPROFILECHANGE is sent to the service handler. Hardware profiles are created with the System applet in the Service Control Panel. Based on the new hardware configuration, the service may modify its ongoing activities.

SERVICE_CONTROL_DEVICEEVENT is the service equivalent to the Win32 message WM_DEVICEEVENT. Inserting, removing, or the transformation of a hardware device triggers this event. With this message, the dwEventType parameter of the handler is the event identifier. This is similar to the WPARAM of WM_DEVICEEVENT. lpEventData is optional event and data equivalent to the LPARAM of WM_DEVICEEVENT.

A service can begin immediately using any new devices. Of course, if a device is no longer available, the service should stop using it. A power event announces a power modulation or vacillation. SERVICE_ACCEPT_POWEREVENT is the service equivalent of the WM_POWERBROADCAST message; dwEventType is the power event identifier, and lpEventData is optional data [Code 2]. This is the WPARAM and LPARAM of WM_POWERBROADCAST, respectively.

Power events afford an opportunity to cache or retrieve data as the result of suspend or resume events.

Code 2

DWORD WINAPI ServiceHandler( DWORD fdwControl,    DWORD dwEventType,
   LPVOID lpEventData, LPVOID lpContext )
{

   // Local variables.

   static DWORD CachedState;
   LPEVENTINFO pEventInfo; 
   DWORD dwBuffer;

   // Do not replicate current state.
   // Controls codes greater than 127 are programmer
   //      defined service control codes.

   if(( CachedState == fdwControl) && ( fdwControl < 128))
      return NO_ERROR;            
   
   // Switch on service control message.
   switch(fdwControl)
   {

      case SERVICE_CONTROL_POWEREVENT:
      {
         switch((int) dwEventType)
            {
            case PBT_APMPOWERSTATUSCHANGE:
               // handle event   
               return NO_ERROR;
            case PBT_APMSUSPEND:
               // handle event
               return NO_ERROR;

            // case PBT_WhatEver and so on.
            }
         }
         break;
   ….
   

Service Control Program Enhancements

In Windows 2000, the SCM monitors and tallies service failures. A service failure occurs when a service closes without reporting a stop event. You can assign specific behavior to the nth failure of a service.

ChangeServiceConfig2 is a polymorphic function that is used to set services failure actions or description. ChangeServiceConfig2 is not an extension of ChangeServiceConfig.

BOOL ChangeServiceConfig2(  SC_HANDLE hService, DWORD dwInfoLevel,   
LPVOID lpInfo )

The first parameter is a service handle. Open the service handle with OpenService or CreateService. Access flags on the service handle should include SERVICE_CHANGE_CONFIG.

To set the failure actions of a service, the second parameter should be SERVICE_CONFIG_FAILURE_ACTIONS. With this flag, the last parameter, lpInfo, is treated as a pointer to a SERVICE_FAILURE_ACTIONS structure.

SERVICE_FAILURE_ACTIONS

DWORD       dwResetPeriod;
LPTSTR       lpRebootMsg;
LPTSTR       lpCommand;
DWORD       cActions;
SC_ACTION *   lpsaActions;

The SCM maintains the count of service failures from the last time the system was booted. In milliseconds, dwResetPeriod establishes a period for resetting the count if no service failure has occurred. If dwResetPeriod is INFINITE, the count is never reset.

An action or command can be associated with a service failure. The lpCommand element is a string reflecting the command line of a chosen operation. Internally, ChangeConfig2 uses CreateProcessAsUser to execute the specified command line.

The member, cActions, is the number of failure events being initialized.

The SC_ACTION structure is a prescription for handling a service failure. There are four choices:

The SC_ACTION structure is:

SC_ACTION

SC_ACTION_TYPE     Type;
DWORD              Delay;

Type is the requested action: SC_ACTION_REBOOT, SC_ACTION_RESTART, SC_ACTION_RUN_COMMAND, and SC_ACTION_NONE. Delay sets a wait time (in milliseconds) before the stipulated action is executed.

For SERVICE_FAILURE_ACTIONS, create an array of SC_ACTION structures. Actions refer to the number of elements in this array. Initialize the SC_ACTION pointer of SERVICE_FAILURE_ACTIONS with the array pointer. The first element of the array sets event handling for the first failure; the next element establishes handling for the second failure, and so on.

To give a service a description, set the dwInfoLevel parameter of ChangeConfigService2 to SERVICE_CONFIG_DESCRIPTION. With this flag, the last parameter (lpInfo) is a pointer to SERVICE_DESCRIPTION. SERVICE_DESCRIPTION has a single element, lpDescription [Code 3].

Code 3

SERVICE_DESCRIPTION sd;
sd.lpDescription="Service Two ( Generic Description )";
hService=OpenService(hSCM, "Service Two", SERVICE_ALL_ACCESS);
ChangeServiceConfig2(hService, 
                               SERVICE_CONFIG_DESCRIPTION,      
                     &sd))
CloseServiceHandle(hService);

Conversely, to query the service's failure actions or description, use QueryServiceConfig2.

BOOL QueryServiceConfig2( SC_HANDLE hService, DWORD dwInfoLevelLPBYTE lpBuffer, 
    DWORD cbBufSize, LPDWORD pcbBytesNeeded)

Initialize the service handle with OpenService or CreateService. Depending on the desired data, the information level is SERVICE_CONFIG_DESCRIPTION or SERVICE_FAILURE_ACTIONS. The next parameter is an out buffer or pointer to SERVICE_DESCRIPTION or SERVICE_FAILURE_ACTIONS, respectively. In cbBufSize, store the size of the buffer. When the call is made, the buffer may not be sufficiently large. When this occurs, pcbBytesNeeded will contain the number of bytes required to complete this request, and GetLastError will return ERROR_INSUFFICIENT_BUFFER.

The SERVICE_CONFIG_DESCRIPTION and SERVICE_FAILURE_ACTIONS structures contain pointers to variable length blocks of memory. QueryServiceConfig2 appends this memory to the end of the structure. You must provide enough memory for the appropriate structure and the appended data. However, this can be difficult to calculate. One strategy to ascertain the appropriate size of the buffer is to invoke QueryServiceConfig2 twice [Code 4]. Call QueryServiceConfig2 with a zero length buffer to determine the size, then create a buffer of the required size, and finally invoke QueryServiceConfig2 again to gather the data.

Code 4

DWORD dwSize;
LPSERVICE_FAILURE_ACTIONS pSFA=NULL;

hService=OpenService(hSCM, "Service Two", SERVICE_ALL_ACCESS);   
QueryServiceConfig2(  hService, SERVICE_CONFIG_FAILURE_ACTIONS,
   NULL, 10, &dwSize);
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
   pSFA=(LPSERVICE_FAILURE_ACTIONS) new BYTE[dwSize];
   QueryServiceConfig2(  hService, 
SERVICE_CONFIG_FAILURE_ACTIONS,
      (LPBYTE) pSFA, dwSize, &dwSize);
}

// Process configuration information.

EnumServiceStatusEx supersedes EnumServiceStatus. It has an additional parameter for enumerating services within a group.

BOOL EnumServicesStatusEx( SC_HANDLE hSCManager, SC_ENUM_TYPE InfoLevel,
       DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices,
DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD lpServicesReturned,
      LPDWORD lpResumeHandle, LPCTSTR pszGroupName)

The last parameter is the name of the service group to be enumerated. If it is an empty string, all services not in a group will be enumerated. If NULL, all services of dwServiceType will be enumerated. As you did with QueryServiceConfig2, invoke EnumServiceStatusEx twice first to determine size and then to receive the information.

Conclusion

The changes in services reflect the practical necessities of services in a real-world environment. These enhancements will be invaluable to service developers as they create solutions for the new millennium.

To focus on topics specific to this article, the program code of this article does not include error handling. When replicating this code, be sure to add the appropriate error handling.