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.
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.
main()
FunctionThe 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.
ServiceMain()
FunctionsThe 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.
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.
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.
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.
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.
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.
SERVICE_STATUS
StructureThe SERVICE_STATUS
structure definition is:
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.
dwServiceType
must be one of the values described in Table 13-1.
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.
dwCurrentState
indicates the current service state. Table 13-2 shows the different possible values.
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.
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 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:
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.
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.
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.
Run 13-2a shows the sc
command tool creating, starting, querying, stopping, and deleting SimpleService
. Only an administrator can perform these steps.
Run 13-2b shows the log file.
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.
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.
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.
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.
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_HANDLE
s.
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).
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.