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

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

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

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

Let’s continue the example. Since I started a Timer in the OnStart(), I better stop it on the OnStop(). Plus, I will write a system event log entry stating that the Windows service was stopped. (Just like OnStart() if the AutoLog is true, the writing of this event log is sort of redundant.)

Here’s all I need to do to shut down this simple example:

virtual void OnStop() override

{

this->timer->Stop(); eventLog1->WriteEntry("SimpleWinService Stopped");

}

OnPause() and OnContinue()

Two common event handlers you may implement are OnPause() and OnContinue(). Obviously these handles are triggered when the Windows services is paused and continued. You use these events to temporarily stop and then later restart any resources that don’t need to be active while the Windows service is paused.

To continue our example, there is no need to have the timer continue to run while the Windows service is paused. (In fact, if you don’t stop the timer, you have not actually paused the service.) When the Windows service finally continues, you obviously need to start the timer back up. Here is the code to handle the pause and continue process:

virtual void OnPause() override

{

this->timer->Stop(); eventLog1->WriteEntry("SimpleWinService Paused");

}

virtual void OnContinue() override

{

eventLog1->WriteEntry("SimpleWinService Continued"); this->timer->Start();

}

The only trick to using these two handlers is that you need to manually add them to your code, but as you can see, it’s hardly rocket science.

Other ServiceBase Class Handlers

There are a few more ServiceBase class handlers that you might use, but I thought it a bit redundant showing examples of using them, as you code them the exact same way as the other handlers. There is one exception, the OnCustomCommand(), but I will hold off covering this handler until I cover managing Windows services later in the chapter.

Customized Example

Okay, SimpleWinService.cpp has remained unchanged from what was generated by the template (or the safe version, if you used that), but we’ve made quite a few changes to SimpleWinService.h. Since some of the code was auto-generated by Visual Studio 2005, I thought it would be helpful for those of you writing a Windows service without VS 2005 to see the customized SimpleWinService.h, shown in Listing 14-4, in its entirety.

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

619

Listing 14-4. Customized SimpleWinService.h Code

#pragma once

using namespace System;

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

namespace Simple

{

private:

double interval;

public ref class SimpleWinService : public ServiceProcess::ServiceBase

{

public:

SimpleWinService()

{

InitializeComponent();

interval = 15000; // 15 seconds - default

}

protected:

~SimpleWinService()

{

if (components)

{

delete components;

}

}

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);

}

}

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

gcnew System::Timers::ElapsedEventHandler(this, &SimpleWinService::timer_Tick);

this->timer->Start();

}

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

virtual void OnStop() override

{

this->timer->Stop(); eventLog1->WriteEntry("SimpleWinService Stopped");

}

virtual void OnPause() override

{

this->timer->Stop(); eventLog1->WriteEntry("SimpleWinService Paused");

}

virtual void OnContinue() override

{

eventLog1->WriteEntry("SimpleWinService Continued"); this->timer->Start();

}

private:

System::Diagnostics::EventLog^ eventLog1; System::Timers::Timer^ timer;

System::ComponentModel::IContainer^ components;

#pragma region Windows Form Designer generated code

void InitializeComponent(void)

{

this->components = (gcnew System::ComponentModel::Container()); this->eventLog1 = (gcnew System::Diagnostics::EventLog()); (cli::safe_cast<System::ComponentModel::ISupportInitialize^>

(this->eventLog1))->BeginInit();

//

//eventLog1

this->eventLog1->Log = L"Application"; this->eventLog1->Source = L"SimpleWinService";

//SimpleWinService

//

this->CanPauseAndContinue = true; this->ServiceName = L"SimpleWinService";

(cli::safe_cast<System::ComponentModel::ISupportInitialize^> (this->eventLog1))->EndInit();

}

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

621

#pragma endregion

private:

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

{

this->timer->Stop();

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

}

};

}

Installing and Uninstalling Windows Services

With other application types, you would now be ready to compile and run. Not so with Windows services.

You now need to add one ServiceProcessInstaller class and a ServiceInstaller class for each service that makes up the Windows service. These classes then are used by the service configuration application to register the Windows service correctly within the registry. To perform the actual registering process, you normally use the service configuration application provided by .NET called InstallUtil.exe. (You can use other installation programs or even write your own, but doing this is beyond the scope of this book.)

Fortunately, the process for creating the ServiceProcessInstaller and ServiceInstaller class is mostly automated. Here are the steps you will follow:

1.Switch to the Design view of the service. You can double-click SimpleWinService.h to do this.

2.Right-click anywhere on the Design view window to bring up the menu as shown in Figure 14-5.

Figure 14-5. Adding installer

3.Select the Add Installer menu item. This will generate ProjectInstaller.cpp and ProjectInstaller.h.

4.Double-click ProjectInstaller.h in the Solution Explorer to switch to the Design view of the

ProjectInstaller.

5.Select the serviceInstaller1 component.

6.Set the ServiceName property to SimpleWinService if it isn’t that already.

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

7.Set the StartType property to Automatic if you want the service to start automatically on startup or leave it as manual if you want the user to start the Windows service. (You might want to leave it as manual for now, as you will most probably be continually starting and stopping the service manually. Once everything is working properly, switch the property to Automatic.)

8.Select the serviceProcessInstaller1 component.

9.Set the Account property to the security context that most suits the need of the Windows service. (Being lazy, I usually just set this to LocalSystem or NetworkService during development and then change it to something more appropriate during final testing and release.)

10.Build your Windows service.

For those of you who are interested, the auto-generated code for ProjectInstall.cpp is shown in Listing 14-5, and ProjectInstaller.h is shown cleaned up in Listing 14-6.

Listing 14-5. Auto-generated ProjectInstall.cpp

#include "StdAfx.h"

#include "ProjectInstaller.h"

Listing 14-6. Auto-generated ProjectInstall.h

#pragma once

using namespace System;

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

using namespace System::Configuration::Install;

namespace Simple

{

[RunInstaller(true)]

public ref class ProjectInstaller :

public System::Configuration::Install::Installer

{

public:

ProjectInstaller(void)

{

InitializeComponent();

}

protected:

~ProjectInstaller()

{

if (components)

{

delete components;

}

}

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

623

private:

System::ServiceProcess::ServiceProcessInstaller^ serviceProcessInstaller1; System::ServiceProcess::ServiceInstaller^ serviceInstaller1; System::Diagnostics::EventLogInstaller^ eventLogInstaller1;

System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code void InitializeComponent(void)

{

this->serviceProcessInstaller1 =

(gcnew System::ServiceProcess::ServiceProcessInstaller()); this->serviceInstaller1 =

(gcnew System::ServiceProcess::ServiceInstaller()); this->eventLogInstaller1 =

(gcnew System::Diagnostics::EventLogInstaller());

//

//serviceProcessInstaller1

this->serviceProcessInstaller1->Account = System::ServiceProcess::ServiceAccount::LocalSystem;

this->serviceProcessInstaller1->Password = nullptr; this->serviceProcessInstaller1->Username = nullptr;

//serviceInstaller1

//

this->serviceInstaller1->ServiceName = L"SimpleWinService";

//

// ProjectInstaller

//

this->Installers->AddRange(

gcnew cli::array<System::Configuration::Install::Installer^>(2)

{

this->serviceProcessInstaller1, this->serviceInstaller1

});

}

#pragma endregion };

}

Now you should have a Windows service that you can finally install and run unless you have added resources to your Windows service that also need to be installed like the system event log. To install the system event log, switch to the Design view of SimpleWinService.h again, right-click the eventLog1 component, and select the Add Installer menu item. Now rebuild the project, and you will have a Windows service ready to be installed.

The first thing you do is navigate in the Visual Studio 2005 Command console window to the directory where your Windows service assembly is located. How you actually install your Windows services depends on which version of SimpleWinService.cpp you used, though behind the scenes, you are really doing the same thing.

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

To install your Windows service using the default template version, you use the command Sample.exe –Install at the command line. (To uninstall, you use the command Simple -Install -u.)

For the safe code version, you use the command InstallUtil Simple.exe at the command line to install the Windows service. (To uninstall, you use the command InstallUtil –u Simple.exe.)

Since both end up doing the same thing behind the scenes, both of the preceding installation methods will cause the output shown in Figure 14-6 to be generated in the command window.

Figure 14-6. Running the installer program

Note that if you selected a security context of User, you will be asked to enter, as shown in Figure 14-7, the user and password you want to run the Windows service under. Make sure you use the full username by including your domain or workgroup as well as the username. For me this is

Amidala\Stephen Fraser.

Figure 14-7. Username and password to run Windows service.

Managing Windows Services

The most basic method of managing Windows services is using the Administrative Tools’ Services application provided by the Windows operating system. For most Windows services, this application will be all that you need. On the other hand, on those rare occasions .NET provides you with the

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

625

System::ServiceProcess::ServiceController class, which enables you to connect and control Windows services within your own custom application.

Services Application

The Administrative Tools’ Services application (see Figure 14-8) is actually a snap-in to the Microsoft Management Console (MMC). Not only does this tool allow you to see, start, pause, continue, stop, and restart all services currently on your system, but it also allows you to perform some configurations on them as well.

Figure 14-8. The Administrative Tools’ Services application

As you can see in Figure 14-8, SimpleWinService is now available on your system, but the status is blank or currently not run. In addition, the startup type is manual and the Windows service uses the LocalSystem security context. Your implementation could be different if you selected different property values for ServiceProcessInstaller and ServicesInstaller.

Double-clicking the SimpleWinService line causes the Services application to present the Windows services properties dialog box as shown in Figure 14-9. The tabs across the top of the control point out the different properties that can be customize for the Windows service:

Change state and startup method of Windows service.

Specify user login security context for the Windows service.

Specify action to perform on Windows service failure.

View Windows service dependencies.

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

Figure 14-9. Services application properties dialog box

I usually only deal with the General tab, where I change the Windows service startup method from Manual (user controlled) to Automatic (system controlled started during startup process). You can also specify stop, start, etc. on the General tab, but I usually use the middle panel on the Services application or use the right-click context menu (see Figure 14-10) associated with the Windows service.

Figure 14-10. Services application Windows services context menu

Custom Service Control Application

There will come a time when the Services application just doesn’t provide enough functionality. When that time comes, the ability to create your own custom service control application really comes in handy.

Usually, you will create a GUI service control application, but nothing is stopping you from making it a console application. In the following examples, I’ll show you a couple of GUI tools, but you should have no trouble making equivalent console applications using the same basic logic.

It is remarkably easy to write your own custom service control application. All that really is required is to add an instance of a System::ServerProcess::ServiceController to your application. Link the controller instance to the Windows service you want to interface with, and then call the

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

627

controller’s methods. The actual communication between the controller and the Windows service is handled for you behind the scenes. Well, to be more accurate, the communication between your controller and the SCM is hidden from you. As mentioned previously, the Windows service gets its handles triggered from the SCM.

Visual Studio 2005 provides a drag and drop interface to simplify the creation of the controller instance for Windows applications. I’ll show the steps here, though the code that gets generated is so simple, it is almost easier just to code it by hand.

1.Create a Windows application. (If you need a reminder on how to do this, I covered how in Chapters 9 and 10.)

2.Open up the Server Explorer.

3.Navigate to the Services branch of the tree as shown in Figure 14-11.

Figure 14-11. Server Explorer, Services branch

4.Select the service you want to interface with.

5.Right-click and select Add to Designer or simply drag the service directly onto the form.

Doing all that adds the following code to your Windows Form, assuming your server is called Amidala and the Windows service is called SimpleWinService:

private: System::ServiceProcess::ServiceController^ serviceController1; this->serviceController1 = (gcnew System::ServiceProcess::ServiceController()); this->serviceController1->MachineName = L"amidala"; this->serviceController1->ServiceName = L"SimpleWinService";