I l @ ve RuBoard |
You can use the classes in the System.ServiceProcess namespace to create new non “device driver services. (If you want to create a device driver service, you must resort to the Microsoft Windows Driver Development Kit [DDK].) When you develop a service, you must also create a service installer that can be used to configure the service and create the appropriate Windows Registry keys. In this section, we'll create a simple service called CakeService. This service will act as a remoting server that hosts the CakeUtils.CakeInfo class that we developed in Chapter 11. The CakeInfo class exposes the FeedsHowMany method, and we'll test the service by creating a remoting client that invokes this method in the CakeService service. The Structure of a Service ApplicationAs we mentioned earlier, a Windows service can be hosted in its own executable program, or the same program can host a number of services. The host program is executed by the Service Control Manager (SCM). The SCM is a part of the Windows operating system that handles requests to start services. Such requests are made when Windows boots as well as through tools that exploit the Windows Service APIs (such as the Services console and the ServiceForm application shown earlier). The SCM also sends other signals (such as Stop , Pause , and Continue ) to a service when directed by Windows Service API calls. Using the same program to contain multiple services is useful if those services have a lot of common functionality and maybe need to share some information. For example, the World Wide Web Publishing Service and the IIS Admin service are obviously related , and are both implemented by the Inetinfo.exe program. In our example, the CakeService service will be hosted in its own executable. Visual Studio .NET supplies a Windows Service template that you can use to get a running start. However, this template is not available in every edition of Visual Studio .NET (it is omitted from the Standard edition, for example), so we'll show you how to create a new service from scratch. The CakeService class (which is available in CakeService.jsl in the CakeService project) was created using the Console Application template in Visual Studio .NET. To make a class into a service, it must extend the System.ServiceProcess.ServiceBase class. (You must also add a reference to the System.ServiceProcess.dll assembly.) The ServiceBase class provides a default implementation of a Windows service, and all you have to do is override its methods and properties and provide your own functionality where needed. At an absolute minimum, you should provide a default constructor in which you initialize the properties of the service and an OnStart method that will be executed when the service is started. You must also provide a main method that creates and runs the service (as explained shortly). The CakeService constructor is executed when the process hosting the service starts running and creates an instance of the CakeService class. You should always invoke the ServiceBase constructor and then set the ServiceName property of the service. You can set other properties that determine how the service will respond to various events (see Table 15-2), but you should not perform any service-specific application logic in the constructor. Instead, you should use the OnStart method, for a couple of reasons:
Notice the corresponding implications for finalization . If your service relies on a finalizer to free resources, the finalizer will not be executed until after the last service hosted by the executable stops and the host process can terminate. If you need to release any resources when a service stops, you should override the OnStop method. The properties that you can set for a service and the methods you should implement are listed in Table 15-2. Table 15-2. ServiceBase Properties
The constructor for the CakeService class is shown below. It sets the ServiceName property to " Cake Service" , indicates that the service can be stopped by an administrator, and enables auto-logging: publicclassCakeServiceextendsServiceBase { publicCakeService() { //CalltheServiceBaseconstructorfirst super(); //ThenameoftheservicethatappearsintheRegistry this.set_ServiceName("CakeService"); //Allowanadministratortostop(andrestart)theservice this.set_CanStop(true); //ReportStartandStopeventstotheWindowseventlog this.set_AutoLog(true); } } The OnStart method registers a remoting channel listening at port 6000 and configures the CakeInfo class in the CakeUtils assembly as a server-activated , singleton object that's accessed through the URL "CakeInfo.rem". (If you need to refresh your memory about how this works, see Chapter 11.) protectedvoidOnStart(String[]args) { //Registerachannel ChannelServices.RegisterChannel(newTcpChannel(6000)); //RegistertheCakeInfoclass RemotingConfiguration.RegisterWellKnownServiceType(Type.GetType("CakeUtils.CakeInfo,CakeUtils"), "CakeInfo.rem", WellKnownObjectMode.Singleton); } You might be interested to observe that OnStart takes a String array as a parameter. When an administrator configures a service using its Properties dialog box from the Services console in Control Panel, she can specify a set of space-delimited strings to be passed to the service, as shown in Figure 15-2. Any values specified will be packaged up into a string array and passed to the OnStart method. Figure 15-2. The Properties dialog box for a service
There is one major caveat with the OnStart method. The Windows operating system will allow your service up to 30 seconds to start. If the service has not started by the end of 30 seconds, Windows will assume that the service has frozen and will shut it down. Therefore, you must make sure that any processing you perform in OnStart will take less than 30 seconds to complete. The CakeService can be stopped, but it has no OnStopped method because there is nothing for the OnStopped method to do in this example. (When the process running the service stops, all remote access will cease automatically because the remote objects will disappear.) Therefore, the default implementation will suffice! The main method of the CakeService class is executed by the SCM when the first service in the host application is started. The main method should call the static Run method of the ServiceBase class to create and run the service: ServiceBase.Run(newCakeService()); The Run method takes an object that implements a service and arranges for the OnStart method to be called. The Run method is overloaded. If the host program implements several services, they should all be instantiated and added to an array of ServiceBase objects. This array should then be passed as the parameter to run. For example ServiceBase[]services=newServiceBase[4]; services[0]=newCakeService(); services[1]=newAnotherService(); services[2]=newYetAnotherService(); services[3]=newAFinalService(); ServiceBase.Run(services); Only the service being started will have its OnStart method invoked at this time, but as other services are sent Start signals their OnStart methods will be executed as well. The complete CakeService class can be downloaded as a part of the CakeService project. Understanding Installer ClassesBefore you can use a service, it must be installed. To install a service, you can define an installer class. You can then use the InstallUtil utility supplied with the .NET Framework SDK to invoke the installer class and install your service. The InstallUtil tool takes an assembly and will attempt to install all components it contains that are tagged with System.ComponentModel.RunInstallerAttribute . You can use the CakeServiceInstaller class (shown later) to install the CakeService service. (It is supplied as part of the CakeService project.) An installer class extends the System.Configuration.Install.Installer class. (You must add a reference to the System.Configuration.Install.dll assembly.) The Installer class provides a basic installation framework, and you override properties, methods, and events as required. An Installer object is associated with a single component. (The term component is used very loosely here; in the CakeService example, the CakeService itself is one component and the host process that executes it is actually another. This is because each of these items has its own set of properties that can be configured independently, as you'll see.) Each component to be installed will need an associated Installer object. If a component comprises many subcomponents, each will have its own associated Installer object. The Installer class contains the Installers property, which is a collection that you populate with Installer objects for configuring these subcomponents. (You can think of the data structure defined by the Installer class and the Installers property as a tree of installers.) The InstallUtil tool invokes the Install method of the top-level Installer object, which installs the component and recursively installs each of the subcomponents by iterating through the Installers property and executing the Install method of each item found. You can subscribe to various events (such as BeforeInstall and AfterInstall ) that are fired as installation progresses if you need to monitor progress. If an exception occurs during installation, the install process might roll back (firing BeforeRollback and AfterRollback events). If the installation is successful, it can be committed, and the Committing and Committed events will be fired. The Installer class also provides the Uninstall method together with the BeforeUninstall and AfterUninstall events. Creating a New InstallerMuch of the time, the default installation methods defined by the Installer class will be adequate; all you need to do for the CakeServiceInstaller class is to populate the Installers property with installers for the service and its host executable, and you can let InstallUtil get on with it. The best place to set the Installers property is the default constructor. ( InstallUtil will instantiate your installer class using the default constructor.) The System.ServiceProcess namespace contains two specialized Installer subclasses for installing services and their host processes. These classes are System.ServiceProcess.ServiceInstaller and System.ServiceProcess.ServiceProcessInstaller , respectively. To install a service, you typically create a single ServiceProcessInstaller object (that describes the service host executable) and as many ServiceInstallers as there are services in the host executable. You should set the properties of the ServiceInstaller object as required by your service. Table 15-3 describes the available properties. Table 15-3. ServiceInstaller Properties
The CakeService example includes only a single service, so the installer will require only a single ServiceInstaller object. The following code (which is taken from the constructor of the CakeServiceInstaller class) creates a ServiceInstaller object and populates the StartType , ServiceName , and DisplayName properties. The CakeService service does not depend on any other services. At the end of the constructor, the ServiceInstaller object is added to the Installers property. privateServiceInstallerserviceInstaller=null; publicCakeServiceInstaller() { serviceInstaller=newServiceInstaller(); serviceInstaller.set_StartType(ServiceStartMode.Manual); serviceInstaller.set_ServiceName("CakeService"); serviceInstaller.set_DisplayName("RemotingCakeSizeServer"); this.get_Installers().Add(serviceInstaller); } In a similar vein, you should also create and set the properties of a Service ProcessInstaller object. This object defines security information that describes the security account the service should use to execute. Table 15-4 describes the available properties. Table 15-4. ServiceProcessInstaller Properties
The following code shows the parts of the CakeServiceInstaller constructor that relate to the ServiceProcessInstaller for the CakeService service: privateServiceProcessInstallerprocessInstaller=null; publicCakeServiceInstaller() { processInstaller=newServiceProcessInstaller(); processInstaller.set_Account(ServiceAccount.LocalService); this.get_Installers().Add(processInstaller); }
Each of the options in the System.ServiceProcess.ServiceAccount enumeration provides a different level of security.
Adding a Service DescriptionIf you examine a service using the Services console, you'll see that a service can have a text description. For some reason, neither the ServiceBase class nor the ServiceInstaller class actually provides the description as a property. The System.ServiceProcess namespace does contain the ServiceProcessDescriptionAttribute class, which looks promising at first glance. But unfortunately , it supplies a description for a service only when it is deployed as a component and used in a visual designer (such as the Windows Forms editor in Visual Studio .NET, when the description appears in the Properties window). You can still set the description for a service, but you must insert it directly into the Windows Registry when the component is installed. The service description should be inserted in the Registry at the key HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\<ServiceName> , where <ServiceName> is the name of the service. You can add a text value labeled as Description to the service key. It would be inconvenient to expect an administrator to perform this task manually after the service has been installed, so you should instead override the Install method of the Installer class and perform the operation there. The Install method for the CakeServiceInstaller class is shown here: importMicrosoft.Win32.*; publicvoidInstall(IDictionarystateSaver) { super.Install(stateSaver); RegistryKeyservice=Registry.LocalMachine.OpenSubKey ("System").OpenSubKey("CurrentControlSet").OpenSubKey ("Services").OpenSubKey(serviceInstaller.get_ServiceName(),true); service.SetValue("Description", "ServicethatprovidesremotingaccesstotheCakeSizeServer"); } Invoke the Install method of the parent class (which will cause the Install method of all the Installer objects in the Installers collection to be called) and add any other functionality you require. In this case, the Install method opens the key for the CakeService and sets the Description value. The RegistryKey and Registry classes are available in the Microsoft.Win32 namespace. Installing and Testing the ServiceIf you build the CakeService project (which comprises the CakeService and CakeServiceInstaller classes), the compiler will generate the assembly CakeService.exe. You can then execute InstallUtil to install the service as shown here: C:\>InstallUtilCakeService.exe Microsoft(R).NETFrameworkInstallationutilityVersion1.0.3705.0 Copyright(C)MicrosoftCorporation1998-2001.Allrightsreserved. Runningatransactedinstallation. BeginningtheInstallphaseoftheinstallation. Seethecontentsofthelogfileforthec:\temp\chapter15\cakeservice\ bin\debug\cakeservice.exeassembly'sprogress. Thefileislocatedatc:\temp\chapter15\cakeservice\bin\debug\ cakeservice.InstallLog. Installingassembly'c:\temp\chapter15\cakeservice\bin\debug\ cakeservice.exe'. Affectedparametersare: assemblypath=c:\temp\chapter15\cakeservice\bin\debug\cakeservice.exe logfile=c:\temp\chapter15\cakeservice\bin\debug\ cakeservice.InstallLog InstallingserviceCakeService... ServiceCakeServicehasbeensuccessfullyinstalled. CreatingEventLogsourceCakeServiceinlogApplication... TheInstallphasecompletedsuccessfully,andtheCommitphaseis beginning. Seethecontentsofthelogfileforthec:\temp\chapter15\cakeservice\ bin\debug\cakeservice.exeassembly'sprogress. Thefileislocatedatc:\temp\chapter15\cakeservice\bin\debug\cakeservice.InstallLog. Committingassembly'c:\temp\chapter15\cakeservice\bin\debug\cakeservice.exe'. Affectedparametersare: assemblypath=c:\temp\chapter15\cakeservice\bin\debug\cakeservice.exe logfile=c:\temp\chapter15\cakeservice\bin\debug\ cakeservice.InstallLog TheCommitphasecompletedsuccessfully. Thetransactedinstallhascompleted. A record of the installation progress will be written to the log files cakeservice.InstallLog and cakeservice.InstallState. The service will appear in the Services console (see Figure 15-3), and you can start and stop the service in the same way you would any other service. Figure 15-3. The Remoting Cake Size Server in the Services console
To test the service, start it and then execute the CakeClient program supplied in the CakeClient project. (This is a copy of the server-activated cake client program from Chapter 11.) The main method of the CakeClient class calls on the CakeService service to create a CakeInfo object, and then it invokes the FeedsHowMany method, passing details describing a 14-inch hexagonal fruit cake. If all is well, you should be informed that the cake will feed 31 people, as shown in Figure 15-4. Figure 15-4. The output of the CakeClient program
Uninstalling a ServiceYou can uninstall a service using InstallUtil, specifying the /u flag: C:\>InstallUtil/uCakeService.exe The InstallUtil program will call the Uninstall method of all classes in the specified assembly that are tagged with RunInstallerAttribute . The default Uninstall method of the Installer class is usually adequate and will arrange for the Uninstall method of all the installers held in the Installers property to be run. If you've overridden the Install method, you might also need to override Uninstall and undo any custom changes made during installation. In the case of the CakeService service, this is not necessary because the CakeService keys are automatically removed from the Registry when the service is uninstalled , taking the Description value with them. |
I l @ ve RuBoard |