Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

608 C H A P T E R 1 4 W I N D O W S S E R V I C E S

Service Control Application

You don’t have to write your own service control application, since the Windows operating system provides one for you, as you saw previously with the Administrative Tools’ Services application. This tool provides limited functionality. It can only start, stop, pause, resume, and restart a Windows service.

When you write your own service control application, you can query and retrieve the properties of the Windows service. Plus, another cool feature of writing your own implementation of the service control application is you can write custom controls that allow you to perform more specialized tasks within the Windows service.

Whenever you use the Services application or your own service control application, you are still using the SCM to communicate with your Windows service. Your service application and service control application only have built-in functionality to communicate via the SCM. You can also use TCP/IP to directly communicate with the service application when the functionality provided by the SCM just doesn’t cut it. Again, since I don’t cover network programming until Chapter 17, I will not show how to write this code, but after you finish Chapter 17, you should have no trouble writing it yourself.

Service Configuration Application

The service configuration application does as its name suggests: it configures the Windows service. Through this application, you specify things like whether the Windows service is started automatically at startup, manually, or is disabled; the user to run the session under; and any dependencies that the services may have.

Windows services normally start when the computer is booted, but you have the option to determine manually when the service will be started. You use the service configuration application to set up the registry and then the service control application to perform the actual startup process. The Windows operating system provides you with a very limited service configuration application, as the Administrative Tools’ Services application handles the setting up of automatic startup, manual startup, and disabling of the Windows service.

The Windows service can be run under four different security context groups as shown in Table 14-2.

Table 14-2. Windows Service Security Contexts

Context

Description

LocalService

Acts as a nonprivileged user on the local computer and uses anony-

 

mous credentials on any remote server

LocalSystem

Acts as a high-privileged user

NetworkService

Acts as a privileged user on the local computer and presents the

 

computer’s credentials to any remote server

User

Uses the privileges available to the specified user (the user may get

 

challenged for a username and password unless both are set within the

 

application)

 

 

The LocalSystem runs the Windows service in a high-privileged security context. Most services do not need this high level of privileges. I recommend the use of LocalService and NetworkService security context instead unless you truly need the high security.

C H A P T E R 1 4 W I N D O W S S E R V I C E S

609

Note LocalService and NetworkService are available only for Windows XP and Windows 2003.

During the startup process, there may be the requirement that certain services be available or loaded first. The service configuration application can be coded to let you know of missing dependencies and preload those dependencies that it has control over.

The ServiceProcess Namespace

Only one namespace is directly related to Windows services: the ServiceProcess namespace. In fact, you normally only have to deal with four of the classes within these namespaces. Table 14-3 shows a brief description of these classes. The rest of the chapter further expands on them.

Table 14-3. ServiceProcess Namespace Classes

Class

Description

ServiceBase

This class is used to create a service application and contains the

 

handlers that your code will interact with.

ServiceController

This class is used to create a service controller application and

 

allows you to connect, stop, start, etc., a Windows service.

ServiceInstaller

This class, along with the ServiceProcessInstaller, is used to

 

create a service configuration application. This class provides

 

properties unique to each service within a Windows service, in

 

particular StartType (Automatic, Manual, Disabled), DisplayName,

 

ServiceName, and ServicesDependentOn.

ServiceProcessInstaller

This class, along with the ServiceInstaller, is used to create a

 

service configuration application. This class provides properties

 

that pertain to all services that are contained within the Windows

 

Service, in particular Username, Password, Context and Account.

 

 

One cool feature of using the preceding classes is that if you are using Visual Studio 2005, many of the properties can be manipulated using the Properties window, so you don’t even have to look at the code. Don’t worry. Not all of the class members can be handled this way. You will still need to write some code.

Creating Windows Services

Okay, I’ve shown you all the pieces, but how do you actually go about creating a Windows service? The example in this section shows you. Here I’ll also explore a new feature to make things interesting: system event logs. The example will use system event logs to log all Windows service handlers that are triggered. The easy route would be to just use file I/O like what I covered in Chapter 8, but since the normal route for logging events in a Windows service is the system event log, I thought I’d do it that way (albeit stripped down and simplified).

But before we get into this new feature, let’s start things off by creating the basic skeleton program from which almost all Windows services emerge.

610 C H A P T E R 1 4 W I N D O W S S E R V I C E S

Like any other application, the first step is to create the base application from a template using Visual Studio 2005. (For those of you doing this from a text editor, you will need to do a bit more typing.) This time the template to select is Windows Service, as shown in Figure 14-2. I gave the new project, created from the template, the name Simple. You will find that the template adds “WinService” to your service names automatically, so I felt it a bit redundant to add some derivative of “Windows service” to the project name. But obviously, you can call your projects anything you want to or change the names created by the template.

Figure 14-2. Selecting the Windows Service template

Unlike most of the other projects created from a Visual Studio 2005 template, this one is not a fully functional application when compiled. You still need to add installers to the project for the Windows service to run. I’ll cover installers later in the chapter. What you do get is the service application part of the Windows service.

The template adds a number of files to the project, but really at this point only two are of interest. If you used “Simple” as your project name, then the two files will be called SimpleWinService.cpp and SimpleWinService.h.

Auto-generated Windows Service

SimpleWinService.cpp, shown in Listing 14-1, is the code automatically generated by the template. This code is basically used to start the registration process of the Windows service.

C H A P T E R 1 4 W I N D O W S S E R V I C E S

611

Listing 14-1. Template-generated SimpleWinService.cpp

#include "stdafx.h" #include <string.h>

#include "SimpleWinService.h"

using namespace Simple;

using namespace System::Text;

using namespace System::Security::Policy; using namespace System::Reflection;

//To install/uninstall the service, type: "Simple.exe -Install [-u]" int _tmain(int argc, _TCHAR* argv[])

{

if (argc >= 2)

{

if (argv[1][0] == _T('/'))

{

argv[1][0] = _T('-');

}

if (_tcsicmp(argv[1], _T("-Install")) == 0)

{

array<String^>^ myargs = System::Environment::GetCommandLineArgs(); array<String^>^ args = gcnew array<String^>(myargs->Length - 1);

// Set args[0] with the full path to the assembly, Assembly^ assem = Assembly::GetExecutingAssembly(); args[0] = assem->Location;

Array::Copy(myargs, 2, args, 1, args->Length - 1); AppDomain^ dom = AppDomain::CreateDomain(L"execDom"); Type^ type = System::Object::typeid;

String^ path = type->Assembly->Location; StringBuilder^ sb =

gcnew StringBuilder(path->Substring(0, path->LastIndexOf(L"\\"))); sb->Append(L"\\InstallUtil.exe");

Evidence^ evidence = gcnew Evidence(); dom->ExecuteAssembly(sb->ToString(), evidence, args);

}

}

else

{

ServiceBase::Run(gcnew SimpleWinService());

}

}

Ugly, don’t you think?

612 C H A P T E R 1 4 W I N D O W S S E R V I C E S

This code is really mostly legacy code from the Managed Extensions for C++ days due to Managed C++’s not being able to generate safe code. You used to need all this code to magically build a command to fool Windows into believing the code is safe. Now with C++/CLI, since safe code can be generated, most of this code can be thrown away. However, it’s probably a good thing to keep this code in your arsenal if you plan on writing a Windows service that isn’t safe. (You’ll learn about unsafe code in Chapters 20 and 21.) On the other hand, if you plan on using safe code, then Listing 14-2 shows how I would change the preceding code.

Listing 14-2. Conversion of SimpleWinService.cpp for Safe Code

#include "stdafx.h"

#include "SimpleWinService.h"

using namespace Simple;

using namespace System::Collections; using namespace System::ServiceProcess;

void main()

{

array<ServiceBase^>^ ServicesToRun;

//More than one user service may run within the same process. To add

//another service to this process, change the following line to

//create a second service object. For example,

//

//ServicesToRun = gcnew array<ServiceBase^>

//{

//gcnew Service1(),

//gcnew Service2()

//};

//

ServicesToRun = gcnew array<ServiceBase^> { gcnew SimpleWinService() };

ServiceBase::Run(ServicesToRun);

}

Notice most of the preceding code is comments. By the way, I can’t lay claim to this code, as it is the code generated by the C# template converted into C++/CLI.

Note To use Listing 14-2, you must compile using the /clr:safe option. If the code compiles cleanly with this option, then you know your code is safe. By the way, you also need to remove the include files from stdafx.h as they contain unsafe code. (You don’t need these include files anyway.)

What’s the big difference between these two listings? Two things are different. The first is that to install Listing 14-1, you use the command Sample.exe –Install, and to uninstall, you use the command Simple -Install -u. For Listing 14-2, you use the command InstallUtil Simple.exe to install and InstallUtil –u Simple.exe to uninstall.

C H A P T E R 1 4 W I N D O W S S E R V I C E S

613

Note Actually, you are using the InstallUtil command for both listings, but the code in Listing 14-1 builds this code behind the scenes.

The second difference in the code generated by Listing 14-1 contains native code and may not be safe, while Listing 14-2 compiles to strictly safe code (if you use the /clr:safe option, that is).

Once you strip away all the magic code, all you are left with is

ServiceBase::Run(ServicesToRun);

All the ServiceBase::Run() method does (at least as far as you need to be concerned about) is load the service application into memory and provide the service-main entry point so that the service control application can start the application.

SimpleWinService.h, shown in Listing 14-3, is a little more exciting, as it is where you will spend most of your time coding the Windows service—in particular, the following two auto-generated handlers:

OnStart(): Used to initialize the Windows service during the startup process

OnStop(): Used to shut down everything opened up while the Windows service was executing

And the four handlers that you most likely will add yourself:

OnContinue(): Runs when the continue event is sent by the Service Control Manager. The handler is used to start up any resources that you might have stopped when you passed the Windows service. This handler is only valid when the Windows service is in a paused state.

OnCustomCommand(): Runs when the SCM sends a custom event to the Windows service.

OnPause(): Runs when the SCM sends a pause event to the Windows service. You use this handler to shut down any resources that don’t need to be active while the Windows service is paused.

OnShutdown(): Runs just before the system shuts down. This is the last chance the Windows service has to shut down any resources that might be left running. Note that this is called when the computer shuts down, not the Windows service.

Listing 14-3. Auto-generated SimpleWinService.h Code

#pragma once

using namespace System;

using namespace System::Collections; using namespace System::ServiceProcess; using namespace System::ComponentModel;

namespace Simple

{

public ref class SimpleWinService : public ServiceProcess::ServiceBase

{

public:

SimpleWinService()

{

InitializeComponent();

}

614 C H A P T E R 1 4 W I N D O W S S E R V I C E S

protected:

~SimpleWinService()

{

if (components)

{

delete components;

}

}

virtual void OnStart(array<String^>^ args) override

{

}

virtual void OnStop() override

{

}

private:

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code

void InitializeComponent(void)

{

this->components = gcnew System::ComponentModel::Container(); this->CanStop = true;

this->CanPauseAndContinue = true; this->AutoLog = true;

this->ServiceName = L"SimpleWinService";

}

#pragma endregion };

}

Listing 14-3 has some interesting Boolean properties that you might want to be aware of (you can change them directly in the code or via the Properties window as shown in Figure 14-3):

AutoLog: You set this to true when you want the Windows service to automatically log entries in the Windows system event log.

CanHandlePowerEvent: You set this to true when you want the Windows service to receive power events like switch for AC power to battery.

CanHandleSessionChangeEvent: You set this to true when you want the Windows service to receive the change event from a Terminal Services session.

CanPauseAndContinue: You set this to true when you want to give the user the ability to pause the Windows service.

CanShutdown: You set this to true when you want the Windows service to receive the Windows Shutdown message.

CanStop: You set this to true if you want the user to be able to shut the Windows service down.

ServiceName: This is the name of the service as it will appear in the Administrative Tools’ Services application.

C H A P T E R 1 4 W I N D O W S S E R V I C E S

615

Figure 14-3. The Windows ServiceProcess Properties View

These properties (except for AutoLog and ServiceName) provide you with a way to restrict which events the Windows Service will receive.

Customizing the Windows Service

Procedurally, there really isn’t much to customizing Windows services. You simply override the handlers provided by ServiceProcess::ServiceBase, which you inherited from automatically when creating a Windows service from the Visual Studio 2005 template. (Of course, the features you want to implement within the Windows service can be any level of complexity.)

Since creating Windows services is basically the same process, I can get away with a very simple example—the logging of all events sent to the Windows service from the SCM.

But before we do this, let’s drag and drop an EventLog control from the components section of the toolbox to the SimpleWinService.h Design view. Once the EventLog icon is on the Design view, click it to enable the Properties window. Right-click the Log property drop-down and then select Application (since you are not logging errors or security info). Finally, in the Source property, enter SimpleWinService. Now, a few additional lines of code are added to your template, and you are ready to create system event logs using the following command:

eventLog1->WriteEntry("An system event log message go here");

Note There are many more options available to you when it comes to configuring system event logs, but they are beyond the scope of this chapter. For more information, consult the .NET Framework documentation.

OnStart()

The first event that your Windows service will probably handle is OnStart(). Frequently, this handler will be used to create a new background thread of execution for the Windows service to run under. Why a separate thread, you are probably asking?

My first thought (and probably yours as well) when I encountered Windows services was that I’d simply start the service in the main thread and then let it run. Then when it needs to pause, continue, or

616 C H A P T E R 1 4 W I N D O W S S E R V I C E S

stop, just handle the event from the main thread. The problem is that a Windows service handler times out after 30 seconds. Thus, if the OnStart() does not return in 30 seconds, the Windows service aborts.

To get around this, you need to have OnStart() return in less than 30 seconds. To do this, you usually create either a System::Timers::Timer or another thread of execution to run your Windows service’s activities and then let the main thread continue and return.

I’ll show the System::Timers::Timer logic in the example, but since I have not covered multithreading yet, I’ll not show any of this code, but once you read Chapter 16, you should have no problem plugging a new thread of execution within the OnStart() handler in place of the Timer event.

Nearly any code you want can be placed in the OnStart() handler so long as it takes less than 30 seconds to execute (and your security context has the privileges to run it).

Here’s a simple and possibly somewhat redundant example of the OnStart() in action. I also for grins and giggles added timer event code and code to dump out the args that were passed:

virtual void OnStart(array<String^>^ args) override

{

eventLog1->WriteEntry("SimpleWinService Started");

if (args->Length == 0) eventLog1->WriteEntry("Empty args");

else

{

for each (String ^s in args)

{

eventLog1->WriteEntry(s);

}

}

double interval = 10000; // 10 seconds - hard coded for simplicity // but could be passed as an argument

this->timer = gcnew System::Timers::Timer(interval); this->timer->Elapsed += gcnew System::Timers::ElapsedEventHandler(this,

&SimpleWinService::timer_Tick);

this->timer->Start();

}

As you can see, the example overrides the virtual handler provided by the Windows Service template, with a log to the system event log stating that the Windows service was started and a dump of the args using the system event log as well.

The code to create the timer is pretty easy. Create a Timer with an interval of 10 seconds. Notice that the timer itself is a member variable code:

private:

System::Timers::Timer^ timer;

The reason the timer is a member variable is that other Windows service handlers are going to need access to it. Next, you add an event handler to the timer’s Elapse handler. Here’s the code for the handler:

private:

void timer_Tick(System::Object^ sender, System::Timers::ElapsedEventArgs^ e)

{

this->timer->Stop();

eventLog1->WriteEntry("SimpleWinService Elapsed Event Occurred"); this->timer->Start();

}

C H A P T E R 1 4 W I N D O W S S E R V I C E S

617

This is pretty much the minimum code you can have for the elapse handler. Notice that you first Stop() the timer, perform whatever actions you want within the elapsed event, and then Start() the timer again. Doing this prevents any problems that might happen if the code takes longer than the timer elapse interval. If you are 100 percent certain that the process time of the handler will be less than the timer’s elapsed time, then you can skip the Stop() and Start() steps.

Now, why is this code of the OnStart() handler redundant? If you have the AutoLog feature set to true, the Windows service will also automatically log this event. Because of this, when you finally get to run this example, you will have two system log events created, one automatically and this one that you created earlier.

This handler is the only one to take an argument, in this case, a string array of values that is sent to it from the Start command. If you use the Services application found in the Administrative Tools, you can add the arguments using the General tab on the Windows service’s Properties window. To do this, select Windows service in the Services application, and then right-click and select the Properties menu item. This should present the Properties dialog window as shown in Figure 14-4. From here, just add the arguments to the Start Parameters text box.

Figure 14-4. Setting starting arguments using Services application

I have yet to use this parameter, but I’m sure it is put to good use by somebody.

OnStop()

The second event that you will most probably have your Windows service handle is OnStop(). Here you place all the code you need to shut down your Windows service.

One thing that bit me once with the OnStop() handler is that it is never called when the CanStop property is set to false, even when the computer is shutting down. Instead, the SCM handles everything itself. (Also, you have no way to stop the Windows service, except killing the process or shutting down the machine.)

One nice thing is that you don’t have to do anything special within the OnStop() handler to trigger the stop event for all the dependent services, as they are automatically triggered by the SCM.