Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1
.pdf628 C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S
Basically, now your application is connected to the Windows service. You have read access (and a few with write access) to a number of the Windows services properties and the ability to trigger the Windows service’s handles by making ServiceController method calls. Table 14-4 shows some of the more common properties and methods available to you.
Table 14-4. Commonly Used ServiceController Properties and Methods
Property/Method |
Description |
CanPauseAndContinue |
A property indicating whether the Windows service can be paused |
|
and continued |
CanShutDown |
A property indicating whether the Windows service receives |
|
shutdown events |
CanStop |
A property indicating whether the Windows service can stop |
|
after starting |
Close() |
A method that closes down this instance of ServicesController and |
|
releases all resources associated with the instance |
Continue() |
A method that triggers the OnContinue() handler |
DependentServices |
A property containing a list of all dependent Windows services |
DisplayName |
A property that allows you to get or set the friendly name of the |
|
Windows service |
ExecuteCommand() |
A method that triggers the OnCustomCommand() handler |
GetServices() |
A static method that retrieves an array of all Windows services on |
|
the system |
MachineName |
A property that allows you to get or set the name of the computer of |
|
where the Windows service resides |
Pause() |
A method that triggers the OnPause() handler |
Refresh() |
A method that refreshes all the Windows Services properties |
ServiceName |
A property that allows you to get or set the name of the service this |
|
instance of ServicesController is referencing |
Start() |
A method that triggers the OnStart() handler |
Status |
A property indicating the current status (state would be more accu- |
|
rate) of the Windows service |
Stop() |
A method that triggers the OnStop() handler |
WaitForStatus() |
A method that waits until the Windows services becomes a specified |
|
status (state) |
|
|
The actual code to implement a custom service control application is nearly trivial. And as far as I can see, there is really only one gotcha. The properties in the ServiceController are a snapshot, and to get the most recent version of them, you need to call the Refresh() method.
To show you what I mean, add four buttons to the Windows Form that you created previously. The form should look something like Figure 14-12.
C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S |
629 |
Figure 14-12. SimpleWinServive controller
Now let’s add the ability for the service control application to start the Windows service. Once you have your Windows Form laid out, double-click the Start button so that you can edit the code for the start Windows service event handler. Here is the code:
System::Void bnStart_Click(System::Object^ sender, System::EventArgs^ e)
{
serviceController1->Refresh();
if (serviceController1->Status == ServiceControllerStatus::Stopped)
{
serviceController1->Start(); MessageBox::Show("SimpleWinService Started");
}
else
{
MessageBox::Show("SimpleWinService Running");
}
}
Yes, that’s all it takes! Now let’s take a closer look. The first thing you need to do is Refresh() the properties. If you don’t, then the Status property will probably be out of sync with the actual Windows service. Then, before you call the Start() method, you need to make sure that the Windows services status is Stopped. Another option would be to enclose the Start() method in a try/catch block, as the Start() method throws an exception if the current start is not Stopped. I added the MessageBoxes so that you can verify all is well, but they are obviously not needed.
Now let’s stop the service:
System::Void bnStart_Click(System::Object^ sender, System::EventArgs^ e)
{
serviceController1->Refresh();
if (serviceController1->Status == ServiceControllerStatus::Stopped)
{
serviceController1->Start(); MessageBox::Show("SimpleWinService Started");
}
else
{
MessageBox::Show("SimpleWinService Running");
}
}
The code is nearly identical. In fact most of the handler trigger methods are handled this exact same way. There is one major exception: ExecuteCommand().
The ExecuteCommand() method allows you to trigger an event on the Windows service based on a numeric value between 128 and 255. Windows reserves the values 0 through 127. The implementation of the custom command is made up of two parts.
630 C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S
First you need to add a call to your Windows Form to ExecuteCommand(), passing it a number representing the command that you want the Windows service to execute. Here is the code for the button Interval 15. (The code for button Interval 20 is virtually the same except the numeric value passed in the ExecuteCommand() method.)
System::Void bnIntv15_Click(System::Object^ sender, System::EventArgs^ e)
{
serviceController1->Refresh();
if (serviceController1->Status == ServiceControllerStatus::Running)
{
serviceController1->ExecuteCommand(150); MessageBox::Show("SimpleWinService Interval in 15 seconds");
}
else
{
MessageBox::Show("SimpleWinService Not Running");
}
}
I’m pretty sure you are starting to see a pattern forming on these event handlers.
The second half the of the custom command is to add an OnCustomCommand() handler to your Windows service, which will process the numeric command sent by the ExecuteCommand() method. Here is an example that changes the interval time of the timer of the Windows service:
virtual void OnCustomCommand(int cmd) override
{
if (cmd == 150) this->timer->Interval = 15000;
else
this->timer->Interval = 20000;
}
I used an if statement due to the fact that only two numeric values are being sent to the OnCustomCommand() handler. Normally, you would probably use a case statement on the cmd parameter.
Normally, I include a full example of the source code, but I see no real added value in doing so for this example, as all the code is so trivial. But if you need the code example, it is available on the Apress and ProCppCLI.net Web sites.
Debugging Windows Services
The process of debugging a Windows service is a little different from the generic Windows Forms application or console application, since you do not start or execute the service via the main() method. Fortunately, all is not lost, as you have two techniques for debugging your Windows service. Which debugging process you use depends on what functionality you are trying to test.
C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S |
631 |
Attaching the Debugger to the Windows Service
The first process is outlined in the many C# books out there that cover Windows services: attach the debugger to the service after it is running. This allows you to use all the standard debugging features available on Visual Studio 2005. The process, while straightforward, is, as far as I’m concerned, far from intuitive. But once you know the steps, you can replicate it for any Windows service.
To attach the debugger to a Windows service requires the following steps:
1.Start your Windows service using the Services application or your own custom service control application.
2.Select from the main Visual Studio 2005 menu Debug and then the menu item Attach to Process. This will display a dialog box similar to the one in Figure 14-13.
Figure 14-13. Attach to Process dialog box
3.Click Show processes from all users. You may not need this if you started the process using your own user security context.
4.Select your Windows service from the Available Processes list.
5.Click the Attach button.
632 C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S
When you complete these steps, the dialog box will disappear, the debugger will be attached to your Windows service, and you will be in debug mode of Visual Studio 2005. At this point, you can set break point, watches, etc., just like you would for any other Windows or console application.
The problem with this method is that you cannot test the OnStart() handler, as it has already run. And executing the OnStop() handler ends the debug session, so you can’t restart the Windows service to test the OnStart() either.
This is where the other testing process comes in.
A Special Main() Function
A Windows service is really just a specialized application. Due to this fact, you can write a slightly modified main() function to test your Windows service’s startup process. I think it’s easier just to show you the code first and walk you through it than try to explain things beforehand. Listing 14-7 shows the new main() method.
Listing 14-7. Debug-enhanced main() Method
#include "stdafx.h"
#include "SimpleWinService.h"
using namespace Simple;
using namespace System::Collections; using namespace System::ServiceProcess;
void main()
{
#ifndef COMMANDLINE_DEBUG array<ServiceBase^>^ ServicesToRun;
ServicesToRun = gcnew array<ServiceBase^> { gcnew SimpleWinService() }; ServiceBase::Run(ServicesToRun);
#else
SimpleWinService ^svc = gcnew SimpleWinService(); svc->OnStart(nullptr);
Console::WriteLine("Any key stop stop"); Console::ReadLine();
svc->OnStop(); #endif
}
The code uses the #ifndef directive (covered in Chapter 4) to split the main() method into two parts. If you recall, the #ifndef directive causes the compiler only to compile code in the enclosed region (between #else, #elseif, or #endif) when the symbol specified does not exist. Thus, the first block compiles the code just like normal, if the symbol COMMANDLINE_DEBUG does not exist, whereas the second block compiles the special code allowing you to debug the OnStart() handler, if the symbol does exist.
You can place the symbol COMMANDLINE_DEBUG either as a #define directive in stdafx.h or in SimpleWinService.h anywhere before the line
#include "SimpleWinService.h"
C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S |
633 |
or in the application’s Processor Definitions property as shown in Figure 14-14. You need to place it before the preceding #include statement because SimpleWinService.h also uses the symbol, as I’ll point out next.
Figure 14-14. Processor Definitions property
One more issue remains. When you compile the preceding code, you get two errors telling you that the OnStart() and OnStop() methods are not accessible. The reason is the auto-generated template code for Windows services defines these two methods as protected and thus not accessible.
To fix this, add
#ifdef COMMANDLINE_DEBUG public:
#endif
right before the call to OnStart(). This will cause the methods to now be public when the symbol is defined.
Now you can compile and debug the Windows service exactly like any other Windows or console application. This, unfortunately, also means you cannot access the Windows service using the Services application or your custom service control application, as it has not actually been started as a service. So long as you don’t try to interface it with either of these, it will behave just like the Windows service does when compiled as a service, with the added bonus that you can now debug the OnStart() method.
By the way, you can debug the other handlers as well by calling them in the main() function.
634 C H A P T E R 1 4 ■ W I N D O W S S E R V I C E S
Summary
Admittedly, this chapter has simplified the coding of Windows services, but you should be well on your way to understanding Windows services after reading it. The chapter started by discussing what a Windows service is and its three parts: service application, service configuration application, and service control application. You moved on by creating a simple Service application. You then saw how to implement a service configuration application using the ServiceProcessInstaller and ServiceInstaller classes. Next, you saw how to use the Windows-provided service control application called the Services application and how to write your own. Finally, you saw two methods for debugging your Windows services.
In the next chapter, you’ll explore a different kind of service, the Web service.
636 C H A P T E R 1 5 ■ W E B S E R V I C E S
Another cool feature of Web services is that they aren’t just a .NET thing. You can access Web services written in any computer language on any platform as long as they conform to an agreedupon set of standards to communicate, nearly always HTTP and SOAP. This feature allows for simple integration of diverse legacy systems and new .NET applications.
For those of you who have been coding for a few years, Web services are a much improved alternative to DCOM, COBRA, and the like.
Components of a Web Service
Web services are based on well-established networking protocols and a few newer technologies. In truth, you really don’t have to know much about any of these technologies because .NET handles them, for the most part, in the background for you. In fact, the first few Web services I wrote were in complete blissful ignorance of these technologies. But, of course, true to my developer nature, I wanted to see what happens behind the curtain.
Basically, for a Web service to function, you need
•A communication protocol so that the service and its consuming client can communicate
•A description service so that the consuming client will be able to understand how to use the Web service
•A discovery service so that the consuming client can find the Web service
In the following sections you’ll take a look at each requirement in a little more detail.
Communication Protocols
Communication between .NET Web services and client consumers is handled via generic HTTP using, normally, port 80. (For those of you who are HTTP knowledgeable, you are aware that HTTP is not restricted to port 80.) If you know something about Internet technology, you will recognize this as the same communication method used by standard Web browsers. Thus, if your system supports a Web browser, it can also support Web services. This is a key aspect of Web services, as other distributed application methods use their own specific communication protocols.
Communication between a Web service and a consumer client is always initiated by the client. Clients communicate with the Web service over HTTP in two different ways:
•HTTP POST commands
•SOAP
If you have done any Web programming, you should be quite comfortable with using HTTP POST commands. Normally, you will not use this method when implementing Web services because it is limited to simple data types for passing between the client and the service.
■Caution Make sure you are using HTTP POST and not HTTP GET. HTTP GET is supported by Web services, but you need to change your default machine.config file. (You must uncomment the line <add name="HttpGet"/>.) My guess is that Microsoft plans to phase this out, so I recommend that you don’t use HTTP GET, and except for basic Web service testing, I don’t really see any reason to use HTTP GET anyway.
C H A P T E R 1 5 ■ W E B S E R V I C E S |
637 |
SOAP is a powerful XML-based protocol that packages up a method call to be executed, along with any parameters it requires for implementing. This package is then sent using a standard HTTP request to the Web service. Upon the completion of the execution of a method, SOAP packages up any return values and sends them back to the client using a standard HTTP response.
The best part of SOAP, at least when it comes to .NET, is you get it for free in almost all cases, and you don't have to know anything about it so long as you code within the Common Language Specification (CLS) specified by .NET. As you will see later in this chapter when I show how to send a DataSet from a Web service to a client, it is possible to transmit fairly complex data objects using SOAP.
Description Service
It’s all well and good that you send stuff back and forth between the client and the Web service. But before this communication can take place, the client needs some way to find out what it can request the Web service to do and what format the request needs to be in. (The format is also known as the method signature.) You might think that you could use SOAP to handle the descriptive service, but SOAP was not designed to describe method signatures, only to package them for transport.
The Web service provides this description of its interfaces using the standard called the Web Services Description Language (WSDL). Like SOAP, WSDL is XML based. But instead of packaging like SOAP, WSDL actually describes the method signatures. In fact, WSDL describes method signatures in such detail that Visual Studio 2005 imports the WSDL’s XML definitions and uses them to provide IntelliSense help.
Like all the previous technologies for Web services, WSDL is completely handled by Visual Studio 2005.
Discovery Service
Even if you can communicate between a client and a Web service and describe how this communication needs to take place, it’s all still moot if the client doesn’t know where to find the required Web service it needs to execute. This is the job of the discovery service. .NET provides two discovery services:
•Web Services Discovery tool (DISCO)
•Universal Description, Discovery, and Integration (UDDI)
DISCO is used to describe each Web service in any given virtual directory and any related subdirectories. Originally, .NET was going to use DISCO as its primary method of discovery, but with the advent of the superior UDDI, DISCO has become optional. It is still created automatically by Visual Studio 2005 for those who want to stick with DISCO, but I think it will most probably disappear in the future.
UDDI’s scope is more far-reaching than DISCO’s. With UDDI, you register your Web service with a central agency. Once your Web service is registered, third parties can search the agency to locate your registered Web service.
Personally, I find discovery services only useful if I don’t know the exact URL of the Web service (which for me is rarely as I am usually the author of the Web service). As you will see later in the chapter, if you know the URL of the Web service you can access it directly without worrying about directory services.