Hello, Service API World

Listing 3-1, the HelloService example, contains the world's simplest ”and perhaps dumbest ”service. The service will do nothing more than beep once per second. Other than being quite annoying, this service will also allow you the opportunity to have concrete proof that the service is running ”even when no user is logged on to the system.

NOTE
The key feature of this service is, of course, the annoying beep that takes place every second or so. So if you are testing this on a machine, it is obviously critical that the speaker is working correctly. Obvious, of course, to everyone but me while I was debugging this code. After many exotic attempts at diagnostics (some of which left artifacts in the final program), I ran the service on another machine. To my horror , it worked perfectly the first time. On further inspection, the audio driver on the initial test machine was not working. The lesson to be learned is that even when you're working on advanced programming problems, making sure the simplest things operate is critical.

Listing 3-1

HelloService.cpp

 #include"stdafx.h" SERVICE_STATUSServiceStatus; SERVICE_STATUS_HANDLEServiceStatusHandle; VOIDWINAPIServiceMain(DWORDargc,LPTSTR*argv); VOIDWINAPIHandler(DWORDdwControl); DWORDServiceInitialization(DWORDargc,LPTSTR*argv, DWORD*specificError); VOIDlogError(char*text); FILE*log; chardiag[255]; BOOLisRunning=false; intmain(intargc,char*argv[]) { SERVICE_TABLE_ENTRYDispatchTable[]= { {"BeepService",ServiceMain}, {0,0} }; sprintf(diag,"Logopened\n"); logError(diag); if(argc>1&&!(stricmp(argv[1],"-i"))) { charszBuffer[255]; charszPath[MAX_PATH]; sprintf(diag,"Ininstallleg\n"); logError(diag); SC_HANDLEscmHandle=OpenSCManager(NULL,NULL, SC_MANAGER_ALL_ACCESS); if(scmHandle==NULL)//Performerrorhandling. { sprintf(diag,"BeepServiceOpenSCManagererror=%d\n", GetLastError()); logError(diag); } SC_HANDLEscHandle; GetModuleFileName(GetModuleHandle(NULL),szPath,MAX_PATH); //Whydowedothis?Thepathmaycontainspaces. //Ifitdoes,thepathmustbequoted. strcpy(szBuffer,"\""); strcat(szBuffer,szPath); strcat(szBuffer,"\""); printf("\nInstallingservice%s",szPath); scHandle=CreateService(scmHandle,"BeeperService", "BeeperServiceForTest",SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, szBuffer,NULL,NULL,NULL,NULL,NULL); if(scHandle==NULL)//Processerror { sprintf(diag,"BeepServiceCreateServiceerror=%d\n", GetLastError()); logError(diag); } CloseServiceHandle(scHandle); CloseServiceHandle(scmHandle); } elseif(argc>1&&!(stricmp(argv[1],"-u"))) { SC_HANDLEscmHandle=OpenSCManager(NULL,NULL, SC_MANAGER_ALL_ACCESS); if(scmHandle==NULL)//Performerrorhandling. { sprintf(diag,"BeepServiceOpenSCManagererror=%d\n", GetLastError()); logError(diag); } SC_HANDLEscHandle; scHandle=OpenService(scmHandle,"BeeperService", SERVICE_ALL_ACCESS); DeleteService(scHandle); printf("\nBeeperServiceuninstalled..."); } else { sprintf(diag,"Abouttostartservicecontrol...\n"); logError(diag); if(!StartServiceCtrlDispatcher(DispatchTable)) { sprintf(diag, "BeepServiceStartServiceCtrlDispatchererror=%d\n", GetLastError()); logError(diag); } } return0; } //Atrivialloggingfunction.Eventloggingwillbeaddedin //Chapter5. VOIDlogError(char*text) { log=fopen("c:\service.log","a+t"); if(log) { fprintf(log,text); fclose(log); } return; } VOIDWINAPIServiceMain(DWORDargc,LPTSTR*argv) { DWORDstatus; DWORDspecificError; ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState=SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted=SERVICE_ACCEPT_STOP; ServiceStatus.dwWin32ExitCode=0; ServiceStatus.dwServiceSpecificExitCode=0; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; ServiceStatusHandle=RegisterServiceCtrlHandler("BeepService", Handler); if(ServiceStatusHandle==(SERVICE_STATUS_HANDLE)0) { sprintf(diag, "BeeperServiceRegisterServiceCtrlHandlerfailed%d\n", GetLastError()); logError(diag); return; } //Initializationcodegoeshere. status=ServiceInitialization(argc,argv,&specificError); //Handleerrorcondition if(status!=NO_ERROR) { ServiceStatus.dwCurrentState=SERVICE_STOPPED; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; ServiceStatus.dwWin32ExitCode=status; ServiceStatus.dwServiceSpecificExitCode=specificError; SetServiceStatus(ServiceStatusHandle,&ServiceStatus); return; } //Initializationcomplete--reportrunningstatus. ServiceStatus.dwCurrentState=SERVICE_RUNNING; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus)) { status=GetLastError(); sprintf(diag, "BeeperServiceSetServiceStatuserror%ld\n",status); logError(diag); } isRunning=true; sprintf(diag,"Justbeforeprocessingloop...\n"); logError(diag); //Thisiswheretheservicedoesitswork. //Firstwedisplayamessageonthedesktop,justbecausewecan! MessageBox(NULL,"HelloServiceWorld","BeeperService", MB_OKMB_SERVICE_NOTIFICATIONMB_ICONINFORMATION); while(1) { Beep(2000,200); Sleep(1000); if(isRunning==false) { break; } } ServiceStatus.dwCurrentState=SERVICE_STOPPED; //Sendcurrentstatus. if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus)) { status=GetLastError(); sprintf(diag, "BeeperServiceSetServiceStatuserror%ld\n",status); logError(diag); } return; } //Stubinitializationfunction. DWORDServiceInitialization(DWORDargc,LPTSTR*argv, DWORD*specificError) { //Justreturnfornow... return(NO_ERROR); } VOIDWINAPIHandler(DWORDdwControl) { DWORDstatus; sprintf(diag,"Intoservicecontrol...\n"); logError(diag); switch(dwControl) { caseSERVICE_CONTROL_PAUSE: //Dowhateverittakestopausehere. ServiceStatus.dwCurrentState=SERVICE_PAUSED; break; caseSERVICE_CONTROL_CONTINUE: //Dowhateverittakestocontinuehere. ServiceStatus.dwCurrentState=SERVICE_RUNNING; break; caseSERVICE_CONTROL_STOP: //Dowhateverittakestostophere. sprintf(diag,"Stopping...\n"); logError(diag); ServiceStatus.dwWin32ExitCode=0; ServiceStatus.dwCurrentState=SERVICE_STOP_PENDING; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; isRunning=false; if(!SetServiceStatus(ServiceStatusHandle, &ServiceStatus)) { status=GetLastError(); sprintf(diag, "BeeperServiceSetServiceStatuserror%ld\n", status); logError(diag); } sprintf(diag, "BeeperServiceleavinghandler\n",0); logError(diag); return; caseSERVICE_CONTROL_INTERROGATE: //Fallthroughtosendcurrentstatus. break; default: sprintf(diag, "BeeperServiceunrecognizedcontrolcode%ld\n", dwControl); logError(diag); } //Sendcurrentstatus. if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus)) { status=GetLastError(); sprintf(diag, "BeeperServiceSetServiceStatuserror%ld\n",status); logError(diag); } return; } 

The main function of a server application often serves multiple purposes. Notice first that the bulk of the main function is an if else if else statement that does different things based on the arguments (if any) that are passed to the program. The actual program can be run directly from the command line with an argument of -I to install the service or -U to uninstall the service. Alternatively, the service can be started by the SCM. One interesting implementation note is that it is critical to check the argument count ( argc ) before checking argv[1] . In a C or C++ program that is run from the command line, argv[0] traditionally would contain the name of the program being run (often containing the full path name ); argv[1] would therefore at least be a NULL that could safely be tested . This is not the case when a service is started.

An array of SERVICE_TABLE_ENTRY structures is declared and allocated at the beginning of the main function. Each element of the array contains the name of the service (BeeperService in this example) as well as the name of the service main function (in this case ServiceMain ). The second entry in the array contains two zeros, indicating the end of the array. If multiple, independent services needed to be run using this single service application, there would be multiple nonzero entries followed by the all-zero entry.

The first lines of code past the initializations are used for some simple message logging. When running, a service application cannot simply use printf statements to show errors. While use of a message box is possible, it is generally better not to display error messages that way, since it is possible no user will be present at the server. The logError function is a simple function that just sends a line of text to a file used to log errors. There is a much better way to record errors and events in the life of the service, which I'll cover in Chapter 5.

Installing the Service

If the first argument to the program is -I , the service should be installed. The first Win32 Service API function called is OpenSCManager . The prototype for this function is

 SC_HANDLEOpenSCManager(LPCTSTRlpMachineName,//Pointertomachinenamestring LPCTSTRlpDatabaseName,//Pointertodatabasenamestring DWORDdwDesiredAccess//Typeofaccess); 

The first argument, lpMachineName , specifies the machine where the SCM should be opened. If this is NULL, the current machine's SCM is opened. The lpDatabaseName parameter is odd. The only documented value for this parameter is SERVICES_ACTIVE_DATABASE, and if this parameter is NULL, SERVICES_ACTIVE_DATABASE is assumed. The final parameter, dwDesiredAccess , specifies the access to the SCM required. This parameter has a number of possible values, including values that will allow locking of the database, enumeration of services, and so on. The generic rights (discussed in the next chapter) are mapped to combinations of the SCM rights. In this example (and in most cases) SC_MANAGER_ALL_ACCESS is requested and granted. You can find details on the possible rights in the SDK documentation.

Using CreateService to Install the Service

To actually install the service, you must call CreateService . This function takes many parameters:

 SC_HANDLECreateService(SC_HANDLEhSCManager,//HandleofSCMdatabase LPCTSTRlpServiceName,//Nameofservicetostart LPCTSTRlpDisplayName,//Displayname DWORDdwDesiredAccess,//Typeofaccesstoservice DWORDdwServiceType,//Typeofservice DWORDdwStartType,//Whentostartservice DWORDdwErrorControl,//Severityofservicefailure LPCTSTRlpBinaryPathName,//Nameofbinaryfile LPCTSTRlpLoadOrderGroup,//Nameofloadorderinggroup LPDWORDlpdwTagId,//Receivestagidentifier LPCTSTRlpDependencies,//Arrayofdependencynames LPCTSTRlpServiceStartName,//Serviceaccountname LPCTSTRlpPassword//Accountpassword); 

The first parameter is the handle returned from OpenSCManager . The next two parameters are the service name and the display name, respectively. It is useful to make the actual service name short and leave out any spaces, since this is the name used if the service is started using the NET START command from the command prompt. The display name is used by the user interface to display the name of the service. The dwDesiredAccess parameter specifies the access to the service that is requested. The constant STANDARD_RIGHTS_REQUIRED includes DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER rights. SERVICE_ALL_ACCESS includes all of these rights plus many rights regarding querying, enumerating, and controlling the service. The generic rights are also mapped to reasonable values, as detailed in the SDK documentation. The simple beep service created by the HelloService program in Listing 3-1 uses SERVICE_ALL_ACCESS. The dwServiceType parameter specifies the type of service from among the options shown in Table 3-1.

This book will not cover kernel or file system drivers, which extend far beyond the scope of this volume.

Table 3-1 Values for the DwServiceType Parameter

Service Type Description
SERVICE_WIN32_OWN_PROCESS Specifies a service that runs within its own process.
SERVICE_WIN32_SHARE_PROCESS Specifies a service that shares its process space with other services.
SERVICE_KERNEL_DRIVER Specifies a driver service.
SERVICE_FILE_SYSTEM_DRIVER Specifies a file system driver.
SERVICE_INTERACTIVE_PROCESS Enables a service that is SERVICE_WIN32_OWN_PROCESS or SERVICE_WIN32_SHARE_PROCESS to interact with the desktop. Valid only if the service is running in the security context of the System account. This constant is ORed with the SERVICE_WIN32_xxx constant.

The SERVICE_INTERACTIVE_PROCESS flag requires some explanation. Services commonly have no way to interact with the currently logged-on user. Generally this is a good thing, because there is no assurance that a user will be at the server console. At times, however, interaction with the user might be required. For these situations, the Windows 2000 development team provided the SERVICE_INTERACTIVE_PROCESS flag. Even this option has some important restrictions. First, this flag is only usable if the service is working under the context of the System account. Furthermore, even if the service installation has requested interactive rights, there is a key in the registry named NoInteractiveServices that controls the effect of the SERVICE_INTERACTIVE_PROCESS flag. This key is found at

 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows 

The default value is 0, meaning that services can interact with the user if this value is set. If this value is nonzero, no service started subsequently ”regardless of the dwServiceType requested ”will be allowed to run interactively. Thus, if the service is for a mass distribution, it is wiser to create a client process that can interact with the service ”perhaps by setting registry values read by the service or by directly communicating with the service through a named pipe or a TCP/IP port. Chapter 12 will use named pipes to interact with a variant of the simple beep service created in Listing 3-1.

There is one other possible way to interact with the user. The MessageBox Win32 API function has the following prototype:

 intMessageBox(HWNDhWnd,//Handleofownerwindow LPCTSTRlpText,//Textinmessagebox LPCTSTRlpCaption,//Messageboxtitle UINTuType//Messageboxstyle); 

This function should be familiar to you, but the MB_SERVICE_NOTIFICATION flag that can be ORed into the uType parameter is likely not as familiar. There is also an MB_SERVICE_NOTIFICATION_NT3X flag that should be used if the service application will be running on Windows NT 3.x. Although Windows NT 3.51 (the last of the 3.x Windows NT line) is quite old now, it is still often used by some of the multiuser systems, such as Citrix Version 1.8. When this flag is set, the hWnd parameter must be NULL so that the message box will be visible on the user's desktop.

The next parameter is dwStartType . This parameter can be set to SERVICE_AUTO_START to start the service automatically upon system startup, to SERVICE_DEMAND_START to allow the service to be started manually as needed, and to SERVICE_DISABLED to indicate that the service is disabled. Other flags detailed in the Win32 SDK allow different starting methods for drivers.

The dwErrorControl parameter specifies how the system should behave if an error occurs while starting the service during system startup. SERVICE_ERROR_IGNORE logs the failure of the service in the event log (discussed in the next chapter) but otherwise continues. SERVICE_ERROR_NORMAL causes the error in starting the service to be logged as well, but also places an error dialog informing the user that the service has failed to start. This is the most common ”and generally the most appropriate ”setting for all but the most mission-critical service applications.

SERVICE_ERROR_SEVERE causes the error to be logged, and if the system is being started with the last known good configuration, startup continues. If the system is not starting up using the last known good configuration, the system is restarted using the last known good configuration. SERVICE_ERROR_CRITICAL behaves similarly to SERVICE_ERROR_SEVERE, except that if the error occurs and the system is already being started using the last known good configuration, the startup operation fails. If a server is dedicated to a single service, it might be appropriate to use one of the last two values for dwErrorControl , because if the service fails to start, server availability is not important.

Commonly, argv[0] of the argv array of pointers passed to main contains the path of the program that is running. This method of getting the path is not as safe and sure as the lpBinaryPathName argument. Instead, the following line of code is used to get the path of the executable to install:

 GetModuleFileName(GetModuleHandle(NULL),szPath,MAX_PATH); 

GetModuleHandle is a Win32 API function that returns a module handle for the module name passed as its only parameter. In the HelloService program, NULL is passed so that the module handle returned is for the file used to create the calling process. This returned value is passed to the Win32 API function GetModuleFileName , along with a pointer to a character buffer ” szPath in the beep example ”and the length of the buffer, MAX_PATH. GetModuleFileName returns the full path name of the module referenced by the module handle passed as its first parameter. This path is copied to another buffer, szBuffer , with leading and trailing quotes. If the module file name contains spaces, the quotes are mandatory, and it does no harm to add quotes around the string if there are no spaces.

The next three parameters to CreateService work together to handle service dependencies that can occur when one service relies on another service. For example, Microsoft Knowledge Base article Q177681 describes a problem in which a service might fail because UNC names that need to be resolved using Domain Name Service (DNS) are being used before the LMHOSTS service is started. The solution in this case is to add the LMHOSTS service to the DependsOnService registry entry at

 HKLM\SYSTEM\CurrentControlSet\Services\LanManWorkstation 

This will let the system know that the LMHOSTS service needs to be started for the LanManWorkstation service to work properly. In other cases, the service that is being installed will know that it is directly dependent on some other service. For instance, a service might require Microsoft SQL Server to properly initialize. In this case, MSSQLServer can be added to the double-NULL- terminated list pointed to by the lpDependencies argument to CreateService . A double-NULL-terminated list is an array of characters in which text values are usually entered, each separated by a NULL, with the last entry followed by two NULLs. Setting this kind of registry entry will be explored in Chapter 12. A somewhat more complicated method for controlling the load order of services is provided by the lpLoadOrderGroup and lpdwTagId parameters. In general, these parameters are used for drivers rather than service applications and are fully documented in the Win32 SDK.

The final two CreateService parameters are used for a user name and a password for an account to start the service, with NULL being used for both lpServiceStartName and lpPassword to indicate that the System account should be used. Remember that if the service is to be interactive, as specified by SERVICE_INTERACTIVE_PROCESS included in the dwServiceType parameter, these entries must be NULL. Also, if the service supports more than a single service by using the SERVICE_WIN32_SHARE_PROCESS flag in the dwServiceType parameter, these entries must be NULL to ensure that the System account is used.

If the handle returned by CreateService is non-NULL, the service has been successfully installed. All that remains is to close the handles that were opened using OpenSCManager and CreateService . Note that in the installation section of the main routine, printf is used to display a progress message. Since the HelloService program is a service, is printf allowed? In this case, yes, because the application is running as a standard Win32 console mode application in the installation portion or, as we will see, the uninstallation portion of the main routine.

Using DeleteService to Uninstall the Service

If -U is passed as the first parameter to the service program, the code to uninstall the service is run. This code is similar to ”but much simpler than ”the installation routine. After the call to OpenSCManager , the uninstallation section of main calls OpenService , which has the following prototype:

 SC_HANDLEOpenService(SC_HANDLE   hSCManager,//Handleofservicecontrolmanager //database LPCTSTR   lpServiceName,//Pointertonameofservicetostart DWORD   dwDesiredAccess//Typeofaccesstoservice); 

The hSCManager parameter is the handle returned by OpenSCManager . The lpServiceName parameter is the name specified previously as the lpServiceName parameter in the call to CreateService . The dwDesiredAccess parameter specifies the access to the service requested, in the same format as CreateService .

Once the handle of the service is returned from OpenService , you can pass it to DeleteService . If the return value is nonzero, the service has been deleted.

Starting the Service

If neither -I nor -U is passed as an argument on the command line, the service assumes that it should start running and the following code segment is run:

 sprintf(diag,"Abouttostartservicecontrol...\n"); logError(diag); if(!StartServiceCtrlDispatcher(DispatchTable)) { sprintf(diag, "BeepServiceStartServiceCtrlDispatchererror=%d\n", GetLastError()); logError(diag); } 

If a service is actually being started by the SCM, the only duty of the main function is to call the Win32 API function StartServiceCtrlDispatcher . This function takes a single argument, a pointer to a SERVICE_TABLE_ENTRY array. (This is the array initialized at the top of the main function.) Once StartServiceCtrlDispatcher is called, main has served its purpose.

Inside ServiceMain

The ServiceMain function is responsible for getting the service running when the service application is started by the SCM. ServiceMain first initializes some elements of the ServiceStatus structure declared at the top of Listing 3-1.

 ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState=SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted=SERVICE_ACCEPT_STOP; ServiceStatus.dwWin32ExitCode=0; ServiceStatus.dwServiceSpecificExitCode=0; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; 

The dwControlsAccepted parameter allows the service to specify which actions it will process. Many services also accept pause requests by ORing SERVICE_ACCEPT_PAUSE_CONTINUE into dwControlsAccepted . The service can also specify that it wants to be notified of a system shutdown by ORing SERVICE_ACCEPT_SHUTDOWN into dwControlsAccepted . Several additional notifications supported in Windows 2000 allow a service to be notified of parameter changes (SERVICE_ACCEPT_PARAMCHANGE), network binding changes (SERVICE_ACCEPT_NETBINDCHANGE), hardware profile changes (SERVICE_ACCEPT_HARDWAREPROFILECHANGE), and power status events (SERVICE_ACCEPT_POWEREVENT). The dwWaitHint parameter allows the service to specify, in milliseconds , how long it expects to take before the next call to notify the SCM of its status. If the time elapses without any notifications, the SCM can assume that an error has occurred.

Once the required ServiceStatus elements have been initialized, the service must call RegisterServiceCtrlHandler for Windows NT or Windows 2000 or RegisterServiceCtrlHandlerEx on Windows 2000. This call should take place before the service executes any initialization code that could be time-consuming . The HelloService code that calls RegisterServiceCtrlHandler is shown here:

 ServiceStatusHandle=RegisterServiceCtrlHandler("BeepService", Handler); if(ServiceStatusHandle==(SERVICE_STATUS_HANDLE)0) { sprintf(diag, "BeeperServiceRegisterServiceCtrlHandlerfailed%d\n", GetLastError()); logError(diag); return; } 

RegisterServiceCtrlHandler is called with the name of the service and the address of the handler function. RegisterServiceCtrlHandlerEx takes the same parameters, except the handler function must accept four parameters rather than one and returns a DWORD value that indicates whether an error has occurred. This code logs an error if the handle returned by RegisterServiceCtrlHandler is NULL.

In Listing 3-1, ServiceInitialization is called next. This is not a Win32 API function, but instead a convenient way to place initialization code outside the ServiceMain function. If the ServiceInitialization function returns an error condition, the dwCurrentState element of the ServiceStatus structure is set to SERVICE_STOPPED with error codes set to indicate the problem ServiceInitialization encountered , and the Win32 API function SetServiceStatus is called.

 BOOLSetServiceStatus(SERVICE_STATUS_HANDLEhServiceStatus,//Servicestatushandle LPSERVICE_STATUSlpServiceStatus//Pointertostatus //structure); 

SetServiceStatus is called frequently while a service is being stopped , started, or paused . This lets the SCM know the current status of the service, allowing the SCM to report this information back to the user via the appropriate user interface.

If the ServiceInitialization call returns NO_ERROR, the beep service sets dwCurrentState to SERVICE_RUNNING and calls SetServiceStatus again to indicate that the service is operational. Note that up to this point, no actual service has taken place. The service is running, but beyond the boilerplate code that installs , uninstalls, and runs the service, we've seen no hint of the service's real purpose.

The actual work of the beep service is simple: Display a message box and then start beeping about once per second.

 //Thisiswheretheservicedoesitswork. MessageBox(NULL,"HelloServiceWorld","BeeperService", MB_OKMB_SERVICE_NOTIFICATIONMB_ICONINFORMATION); while(1) { Beep(2000,200); Sleep(1000); if(isRunning==false) { break; } } 

The processing loop checks the status of the BOOL flag isRunning . When the flag becomes false, the service exits. This is a reasonable action for a simple test application because we are not allowing anything but the stopping of the service. No pause or continue is possible.

Once the beep service exits the processing loop, the final step is to call SetServiceStatus one more time to let the SCM know that the service has stopped. Skipping this final step will result in the service stopping, but the SCM will register an error stopping the service.

NOTE
What happens when a service is paused? Should all operations immediately stop? Should ongoing operations continue to completion but no additional work commence? The answers depend on the duration of the operations involved, as well as the criticality of the processing, and the decision is left completely to the developer. If a service were controlling some materials processing that would cause a large amount of waste if the processing were stopped in the middle, running a particular batch to completion in the event of a pause request would be reasonable. If a service were servicing a client and the average client connection was on the order of several seconds, continuing processing for existing clients would likely make sense. On the other hand, if the service is a database engine or a Web server, pausing only new connections while allowing ongoing connections might not make sense. In the final analysis, the developer must control the semantics of pausing, and should of course document the effects of the pause operation so that the user of the service can react appropriately in the event that service stops responding.

The Handler function in this example is pretty plain vanilla in its behavior. In the HelloService example, the only special handling is for the SERVICE_CONTROL_STOP case of the switch statement. One problem with the code is the handling of the SERVICE_CONTROL_PAUSE case and the SERVICE_CONTROL_CONTINUE case of the switch statement. For instance, if there were code that began the pausing of the service, that code could be run twice if two pause commands were sent within the time it takes for the system to pause. This is not a problem in the Windows 2000 user interface, as the pause button is commonly disabled after a pause request is made. However, services can be controlled by other applications, and those applications might not be so considerate. The C++ Beep Service example later in this chapter will solve these problems and a few others.



Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net