Chapter 13. Windows Services

The server programs in Chapters 11 and 12 are console applications. In principle, the servers could run indefinitely, serving numerous clients as they connect, send requests, receive responses, and disconnect. That is, these servers could provide continuous services, but to be fully effective, the services should be manageable.

Windows Services,1 previously known as NT Services, provide the management capabilities required to convert the servers into services that can be initiated on command or at boot time, before any user logs in, and can also be paused, resumed, terminated, and monitored. The registry maintains information about services.

1 This terminology can be confusing because Windows provides numerous services that are not the Windows Services described here. However, the context should make the meaning clear, just as using the term “Windows” throughout the book when talking specifically about the API has not been a problem.

Ultimately, any server, such as those developed in Chapters 11 and 12, should be converted to a service, especially if it is to be widely used by customers or within an organization.

Windows provides a number of services; examples include the DNS Client, several SQL Server services, and Terminal Services. The computer management snap-in, accessible from the Control Panel, displays the full set of services.

Chapter 6’s JobShell (Program 6-3) provides rudimentary server management by allowing you to bring up a server under job control and send a termination signal. Windows Services, however, are much more comprehensive and robust, and the main example is a conversion of JobShell so that it can control Windows Services.

This chapter also shows how to convert an existing console application into a Windows service and how to install, monitor, and control the service. Logging, which allows a service to log its actions to a file, is also described.

Writing Windows Services—Overview

Windows Services run under the control of a Service Control Manager (SCM). You can interact with the SCM to control services in three ways:

1. Use the management snap-in labeled Services under Systems and Maintenance, Administrative Tools in the Control Panel.

2. Control services with the sc.exe command line tool.

3. Control the SCM programmatically, as Program 13-3 demonstrates.

Converting a console application, such as serverNP or serverSK, to a Windows Service requires three major steps to place the program under the SCM.

1. Create a new main() entry point that registers the service with the SCM, supplying the logical service entry points and names.

2. Convert the old main() entry point function to ServiceMain(), which registers a service control handler and informs the SCM of its status. The remaining code is essentially that of the existing program, although you can add event logging commands. The name ServiceMain() is a placeholder for the name of a logical service, and there can be one or more logical services in a single process.

3. Write the service control handler function to respond to commands from the SCM.

As we describe these three steps, there are several references to creating, starting, and controlling services. Later sections describe the specifics, and Figure 13-1, later in the chapter, illustrates the component interactions.

The main() Function

The new main() function, which the SCM calls, has the task of registering the service with the SCM and starting the service control dispatcher. This requires a call to the StartServiceCtrlDispatcher function with the name(s) and entry point(s) of one or more logical services.

The single parameter, lpServiceStartTable, is the address of an array of SERVICE_TABLE_ENTRY items, where each item is a logical service name and entry point. The end of the array is indicated by a pair of NULL entries.

The return is TRUE if the registration was successful.

The main thread of the service process that calls StartServiceCtrlDispatcher connects the thread to the SCM. The SCM registers the service(s) with the calling thread as the service control dispatcher thread. The SCM does not return to the calling thread until all services have terminated. Notice, however, that the logical services do not actually start at this time; starting the service requires the StartService function, which we describe later.

Program 13-1 shows a typical service main program with a single logical service.

Program 13-1 main: The Main Service Entry Point

image

ServiceMain() Functions

The dispatch table specifies the functions, as shown in Program 13-1, and each function represents a logical service. The functions are enhanced versions of the base program that is being converted to a service, and the SCM invokes each logical service on its own thread. A logical service may, in turn, start up additional threads, such as the server worker threads that serverSK and serverNP create. Frequently, there is just one logical service within a Windows Service. In Program 13-2, the logical service is adapted from the main server (Program 12-2). It would be possible, however, to run both socket and named pipe logical services under the same Windows service, in which case you would supply two service main functions.

Program 13-2 SimpleService: A Service Wrapper

image

image

image

image

image

image

image

While the ServiceMain() function is an adaptation of a main() function with argument count and argument string parameters, there is one small change. The function should be declared void WINAPI rather than having an int return of a normal main() function.

Registering the Service Control Handler

A service control handler, called by the SCM, must be able to control the associated logical service. The console control handler in serverSK, which sets a global shutdown flag, illustrates, in limited form, what is expected of a handler. First, however, each logical service must immediately register a handler using RegisterServiceCtrlHandlerEx. The function call should be at the beginning of ServiceMain() and not called again. The SCM, after receiving a control request for the service, calls the handler.

Parameters

lpServiceName is the user-supplied service name provided in the service table entry for this logical service; it should match a ServiceMain function name registered with StartServiceCtrlDispatcher.

lpHandlerProc is the address of the extended handler function, described in a later section.

lpContext is user-defined data passed to the control handler. This allows a single control handler to distinguish between multiple services using the same handler.

The return value, which is a SERVICE_STATUS_HANDLE object, is 0 if there is an error, and the usual methods can be used to analyze errors.

Setting the Service Status

Now that the handler is registered, the next immediate task is to set the service status to SERVICE_START_PENDING using SetServiceStatus. SetServiceStatus will also be used in several other places to set different values, informing the SCM of the service’s current status. A later section and Table 13-3 describe the valid status values in addition to SERVICE_START_PENDING.

The service control handler must set the status every time it is called, even if there is no status change.

Furthermore, any of the service’s threads can call SetServiceStatus at any time to report progress, errors, or other information, and services frequently have a thread dedicated to periodic status updates. The time period between status update calls is specified in a member field in a data structure parameter. The SCM can assume an error has occurred if a status update does not occur within this time period.

Parameters

hServiceStatus is the SERVICE_STATUS_HANDLE returned by RegisterServiceCtrlHandlerEx. The RegisterServiceCtrlHandlerEx call must therefore precede the SetServiceStatus call.

lpServiceStatus, pointing to a SERVICE_STATUS structure, describes service properties, status, and capabilities.

The SERVICE_STATUS Structure

The SERVICE_STATUS structure definition is:

Parameters

dwWin32ExitCode is the normal thread exit code for the logical service. The service must set this to NO_ERROR while running and on normal termination. Despite the name, you can use this field on 64-bit applications; there will be “32” references in other nSames.

dwServiceSpecificExitCode can be used to indicate an error while the service is starting or stopping, but this value will be ignored unless dwWin32ExitCode is set to ERROR_SERVICE_SPECIFIC_ERROR.

dwCheckPoint should be incremented periodically by the service to report its progress during all steps, including initialization and shutdown. This value is invalid and should be 0 if the service does not have a start, stop, pause, or continue operation pending.

dwWaitHint, in milliseconds, is the elapsed time between calls to SetServiceStatus with either an incremented value of dwCheckPoint value or a change in dwCurrentState. As mentioned previously, the SCM can assume that an error has occurred if this time period passes without such a SetServiceStatus call.

The remaining SERVICE_STATUS members are now described in individual sections.

Service Type

dwServiceType must be one of the values described in Table 13-1.

Table 13-1 Service Types

image

For our purposes, the type is almost always SERVICE_WIN32_OWN_PROCESS, and SERVICE_WIN32_SHARE_PROCESS is the only other value suitable for user-mode services. Showing the different values, however, does indicate that services play many different roles.

Service State

dwCurrentState indicates the current service state. Table 13-2 shows the different possible values.

Table 13-2 Service State Values

image

Controls Accepted

dwControlsAccepted specifies the control codes that the service will accept and process through its service control handler (see the next section). Table 13-3 enumerates three values used in a later example, and the appropriate values should be combined by bit-wise “or” (|). See the MSDN entry for SERVICE_STATUS for the three additional values.

Table 13-3 Controls That a Service Accepts (Partial List)

image

Service-Specific Code

Once the handler has been registered and the service status has been set to SERVICE_START_PENDING, the service can initialize itself and set its status again. In the case of converting serverSK, once the sockets are initialized and the server is ready to accept clients, the status should be set to SERVICE_RUNNING.

The Service Control Handler

The service control handler, the callback function specified in RegisterServiceCtrlHandlerEx, has the following form:

The dwControl parameter indicates the actual control signal sent by the SCM that should be processed.

There are 14 possible values for dwControl, including the controls mentioned in Table 13-3. Five control values of interest in the example are listed here:

image

User-defined values in the range 128–255 are also permitted but will not be used here.

dwEventType is usually 0, but nonzero values are used for device management, which is out of scope for this book. lpEventData provides additional data required by some of these events.

Finally, lpContext is user-defined data passed to RegisterServiceCtrlHandlerEx when the handler was registered.

The handler is invoked by the SCM in the same thread as the main program, and the function is usually written as a switch statement. This is shown in the examples.

Event Logging

Services run “headless” without user interaction, so it is not generally appropriate for a service to display status messages directly. Prior to Vista and NT6, some services would create a console, message box, or window for user interaction; those techniques are no longer available.

The solution is to log events to a log file or use Windows event logging functionality. Such events are maintained within Windows and can be viewed from the event viewer provided in the control panel’s Administrative Tools.

The upcoming SimpleService example (Program 13-2) logs significant service events and errors to a log file; an exercise asks you to modify the program to use Windows events.

Example: A Service “Wrapper”

Program 13-2 performs the conversion of an arbitrary _tmain to a service. The conversion to a service depends on carrying out the tasks we’ve described. The existing server code (that is, the old _tmain function) is invoked as a thread or process from the function ServiceSpecific. Therefore, the code here is essentially a wrapper around an existing server program.

The command line option -c specifies that the program is to run as a stand-alone program, perhaps for debugging. Without the option, there is a call to StartServiceCtrlDispatcher.

Another addition is a log file; the name is hard-coded for simplicity. The service logs significant events to that file. Simple functions to initialize and close the log and to log messages are at the end.

Several other simplifications and limitations are noted in the comments.

Running the Simple Service

Run 13-2a shows the sc command tool creating, starting, querying, stopping, and deleting SimpleService. Only an administrator can perform these steps.

Run 13-2a SimpleService: Controlled by sc

image

Run 13-2b shows the log file.

Run 13-2b SimpleServiceLog.txt: The Log File

image

Managing Windows Services

Once a service has been written, the next task is to put the service under the control of the SCM so that the SCM can start, stop, and otherwise control the service. While sc.exe and the Services administrative tool can do this, you can also manage services programmatically, as we’ll do next.

There are several steps to open the SCM, create a service under SCM control, and then start the service. These steps do not directly control the service; they are directives to the SCM, which in turn controls the specified service.

Opening the SCM

A separate process, running as “Administrator,” is necessary to create the service, much as JobShell (Chapter 6) starts jobs. The first step is to open the SCM, obtaining a handle that then allows the service creation.

Parameters

lpMachineName is NULL if the SCM is on the local computer, but you can also access the SCM on networked machines.

lpDatabaseName is also normally NULL.

dwDesiredAccess is normally SC_MANAGER_ALL_ACCESS, but you can specify more limited access rights, as described in the on-line documentation.

Creating and Deleting a Service

Call CreateService to register a service.

As part of CreateService operation, new services are entered into the registry under:

HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices

Do not, however, attempt to bypass CreateService by manipulating the registry directly; we just point this out to indicate how Windows keeps service information.

Parameters

hSCManager is the SC_HANDLE obtained from OpenSCManager.

lpServiceName is the name used for future references to the service and is one of the logical service names specified in the dispatch table in the StartServiceCtrlDispatcher call. Notice that there is a separate CreateService call for each logical service.

lpDisplayName is the name displayed to the user to represent the service in the Services administrative tool (accessed from the Control Panel under Administrative Tools) and elsewhere. You will see this name entered immediately after a successful CreateService call.

dwDesiredAccess can be SERVICE_ALL_ACCESS or combinations of GENERIC_READ, GENERIC_WRITE, and GENERIC_EXECUTE. See the MSDN documentation for additional details.

dwServiceType has values as in Table 13-1.

dwStartType specifies how the service is started. SERVICE_DEMAND_START is used in our examples, but other values (SERVICE_BOOT_START and SERVICE_SYSTEM_START) allow device driver services to be started at boot time or at system start time, and SERVICE_AUTO_START specifies that a service is to be started at machine start-up.

lpBinaryPathName gives the service’s executable as a full path; the .exe extension is necessary. Use quotes if the path contains spaces.

Other parameters specify account name and password, groups for combining services, and dependencies when there are several interdependent services.

Service configuration parameters of an existing service can be changed with ChangeServiceConfig and ChangeServiceConfig2, which is simpler and is not, perhaps for that reason, called ChangeServiceConfigEx. Identify the service by its handle, and you can specify new values for most of the parameters. For example, you can provide a new dwServiceType or dwStartType value but not a new value for dwAccess.

There is also an OpenService function to obtain a handle to a named service. Use DeleteService to unregister a service from the SCM and CloseServiceHandle to close SC_HANDLEs.

Starting a Service

A service, once created, is not running. Start the ServiceMain() function by specifying the handle obtained from CreateService along with the argc, argv command line parameters expected by the service’s main function (that is, the function specified in the dispatch table).

Controlling a Service

Control a service by telling the SCM to invoke the service’s control handler with the specified control.

The interesting dwControlCode values for our examples are:

image

or a user-specified value in the range 128–255. Additional named values notify a service that start-up values have changed or there are changes related to binding.

SERVICE_CONTROL_INTERROGATE tells the service to report its status with SetServiceStatus, but it’s of limited use, as the SCM receives periodic updates.

lpServStat points to a SERVICE_STATUS structure that receives the current status. This is the same structure as that used by the SetServiceStatus function.

Querying Service Status

Obtain a service’s current status in a SERVICE_STATUS structure with the following:

There’s a distinction between calling QueryServiceStatus, which gets the current status information from the SCM, and ControlService with a SERVICE_CONTROL_INTERROGATE control code. The former tells the service to update the SCM rather than the application program.

Summary: Service Operation and Management

Figure 13-1 shows the SCM and its relation to the services and to a service control program, such as the one in Program 13-3 in the next section. In particular, a service must register with the SCM, and all commands to the service pass through the SCM.

Figure 13-1 Controlling Windows Services through the SCM

image

Program 13-3 ServiceShell: A Service Control Program

image

image

image

image

image

image

image

image

Example: A Service Control Shell

You can control Windows Services from the Administrative Tools, where there is a Services icon. Alternatively, you can control services from the Windows command sc.exe. Finally, you can control a service from within an application, as illustrated in the next example, ServiceShell (Program 13-3), which is a modification of Chapter 6’s JobShell (Program 6-3).

This example is intended to show how to control services from a program; it does not supplant sc.exe or the Services Administrative tool.

Run 13-3 shows SimpleService operation.

Run 13-3 ServiceShell: Managing Services

image

Sharing Kernel Objects with a Service

There can be situations in which a service and applications share a kernel object. For example, the service might use a named mutex to protect a shared memory region used to communicate with applications. Furthermore, in this example, the file mapping would also be a shared kernel object.

There is a difficulty caused by the fact that applications run in a security context separate from that of services, which can run under the system account. Even if no protection is required, it is not adequate to create and/or open the shared kernel objects with a NULL security attribute pointer (see Chapter 15). Instead, a non-NULL discretionary access control list is required at the very least—that is, the applications and the service need to use a non-NULL security attribute structure. In general, you may want to secure the objects, and, again, this is the subject of Chapter 15.

Also notice that if a service runs under the system account, there can be difficulties in accessing resources on other machines, such as shared files, from within a service.

Notes on Debugging a Service

A service is expected to run continuously, so it must be reliable and as defect-free as possible. While a service can be attached to the debugger and event logs can be used to trace service operation, these techniques are most appropriate after a service has been deployed.

During initial development and debugging, however, it is often easier to take advantage of the service wrapper presented in Program 13-2, which allows operation as either a service or a stand-alone application based on the command line -c option.

• Develop the “preservice” version as a stand-alone program. serverSK, for example, was developed in this way.

• Instrument the program with event logging or a log file.

• Once the program is judged to be ready for deployment as a service, run it without the -c command line option so that it runs as a service.

• Additional testing on a service is essential to detect both additional logic errors and security issues. Services can run under the system account and do not, for instance, necessarily have access to user objects, and the stand-alone version may not detect such problems.

• Normal events and minor maintenance debugging can be performed using information in the log file or event log. Even the status information can help determine server health and defect symptoms.

• If extensive maintenance is necessary, you can debug as a normal application using the -c option.

Summary

Windows services provide standardized capabilities to add user-developed services to Windows computers. An existing stand-alone program can be converted to a service using the methods in this chapter.

A service can be created, controlled, and monitored using the Administrative Tools or the ServiceShell program presented in this chapter. The SCM controls and monitors deployed services, and there are registry entries for all services.

Looking Ahead

Chapter 14 describes asynchronous I/O, which provides two techniques that allow multiple read and write operations to take place concurrently with other processing. It is not necessary to use threads; only one user thread is required.

In most cases, multiple threads are easier to program than asynchronous I/O, and thread performance is generally superior. However, asynchronous I/O is essential to the use of I/O completion ports, which are extremely useful when building scalable servers that can handle large numbers of clients.

Chapter 14 also describes waitable timers.

Additional Reading

Kevin Miller’s Professional NT Services thoroughly covers the subject. Device drivers and their interaction with services were not covered in this chapter; a book such as Walter Oney’s Programming the Microsoft Windows Driver Model, Second Edition, can provide that information.

Exercises

13–1. Modify Program 13-2 (SimpleService) to use Windows events instead of a log file. The principal functions to use are RegisterEventSource, ReportEvent, and DeregisterEventSource, all described in MSDN. Also consider using Vista event logging. Alternatively, use an open source logging system such as NLog (http://nlog-project.org/home).

13–2. Extend serviceSK to accept pause controls in a meaningful way. As suggested behavior for a paused service, it should maintain existing connections but not accept new connections. Furthermore, it should complete and respond to requests that are currently being processed, but it should not accept any more client requests.

13–3. ServiceShell, when interrogating service status, simply prints out the numbers. Extend it so that status is presented in a more readable form.

13–4. Convert serverNP (Program 12-3) into a service.

13–5. Test serviceSK in the Exercises file. Modify serviceSK so that it uses event logging.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.207.164