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

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

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

598 C H A P T E R 1 3 X M L

That was simple enough. Let’s look at something a little more complex. It is possible to specify that you don’t care what the parents are by prefixing with a double forward slash (//). For example,

//Name

would get you all Name nodes in the document. Be careful, though: If you use the Name element start tag in different places, you will get them all.

Along the same lines, if you don’t actually care what the parent is but you want only a node at a specific depth, you would use the asterisk (*) to match any element. For example,

/MonsterList/*/Name

would get all the names with a grandparent of MonsterList, but it would matter who the parent was. Conditional expressions are possible. You enclose conditionals in square brackets ([]). For

example,

//Monster[Name]

would result in all monsters that have the Name node (which would be all of them, as Name is a mandatory element—but that is another story). It is possible to specify an exact value for the conditional node or specify what values it cannot be. For example,

//Monster[Name = ''Goblin''] //Monster[Name != ''Succubus'']

would result in the first expression grabbing the Monster node Goblin and the second expression grabbing every monster but the Succubus.

Here is a method that will execute a combination of a few of the expressions you covered previously. Also notice that just to be different, the example uses the XmlNode class’s SelectNodes() method. Because XmlNodes don’t concatenate child values, you need to navigate to the child to get the desired value:

void GetDragonsWeapons(XmlNode ^node)

{

XmlNodeList ^list =

node->SelectNodes("//Monster[Name='Red Dragon']/Weapon"); Console::WriteLine("\nDragon's Weapons\n-------");

IEnumerator ^en = list->GetEnumerator(); while (en->MoveNext())

{

XmlNode ^n = (XmlNode^)en->Current; Console::WriteLine(n->FirstChild->Value);

}

}

Figure 13-10 shows the output of the snippet.

Figure 13-10. Output for the XPath expression //Monster[Name='Red Dragon']/Weapon

C H A P T E R 1 3 X M L

599

Let’s expand on this expression just a little more. It is also possible to have conditionals with logical operators such as and, or, and not().

The following method shows the logical operator in practice. It also shows how to grab an attribute value out of the navigator:

void GetGoblinSuccubusHitDice(XPathNavigator ^nav)

{

XPathNodeIterator ^list =

nav->Select("//Monster[Name='Goblin' or Name='Succubus']/HitDice");

Console::WriteLine("\nGoblin & Succubus HD\n-----------"); while (list->MoveNext())

{

XPathNavigator ^n = list->Current; n->MoveToFirstAttribute(); Console::WriteLine(n->Value);

}

}

Figure 13-11 shows the output of the snippet.

Figure 13-11. Output for the XPath expression //Monster[Name='Goblin' or Name='Succubus']/HitDice

To match attributes in an XPath expression, use the “at” sign (@) in front of the attribute’s name. For example,

void GetGoblinSuccubusHitDice(XPathNavigator ^nav)

{

XPathNodeIterator ^list =

nav->Select("//Monster[Name='Goblin' or Name='Succubus']/HitDice/@Dice");

Console::WriteLine("\nGoblin & Succubus HD\n----------"); while (list->MoveNext())

{

XPathNavigator ^n = list->Current; Console::WriteLine(n->Value);

}

}

results in the same output as the previous example. Notice that you no longer have to move to the attribute before displaying it.

As a final example, the following snippet shows that you can make numeric comparisons. In this example, I grab all Weapon elements with a Number attribute of less than or equal to 1:

600 C H A P T E R 1 3 X M L

void GetSingleAttackWeapons(XPathNavigator ^nav)

{

XPathNodeIterator ^list =

nav->Select("//Weapon[@Number <= 1]");

 

Console::WriteLine("\nSingle Attack Weapons\n----------

");

while (list->MoveNext())

 

{

 

XPathNavigator ^n = list->Current;

 

Console::WriteLine(n->Value);

 

}

 

}

 

Figure 13-12 shows the output of the snippet.

Figure 13-12. Output for the XPath expression //Weapon[@Number <= 1]

Table 13-4 is a list of the available operators that you have at your disposal when developing your XPath expressions.

Table 13-4. XPath Operators

Operator Description

|Compute the union of node sets; for example: //monsters | //players would return a node set containing all monster and players (if players were part of the DOM)

+Addition

-Subtraction

*Multiplication

div Division

=Equals

!=

Not equals

<Less than

<=

Less than or equal to

>Greater than

>=

Greater than or equal to

or

Or

C H A P T E R 1 3 X M L

601

Table 13-4. XPath Operators

Operator

Description

and

And

mod

Modulus (remainder)

not

Negation

 

 

XML and ADO.NET

This topic almost doesn’t merit a section of its own, as only one class, XmlDataDocument, needs to be examined, and XmlDataDocument inherits from XmlDocument. What am I trying to get at? To use ADO.NET and XML together, you need to create a DataSet (see Chapter 12) and create an XmlDataDocument with it. Then you can manipulate the database data just as you did with XmlDocument.

The XmlDataDocument class adds properties and members to streamline some activities and to make them more “relational database”–like, but other than that you have already learned what you need to work with XML originating from an ADO.NET database:

DataSet is the DataSet used to create the XmlDataDocument.

CreateEntityReference() is a method that is not supported and throws an exception.

GetElementById() is a method that is not supported and throws an exception.

GetElementFromRow() gets an XmlElement associated with a specified DataRow.

GetRowFromElement() gets a DataRow associated with a specified XmlElement.

Load() loads into the XmlDocument using a filename, Stream, TextReader, or XmlReader, and then synchronizes with the DataSet.

The example in Listing 13-13 is an exact duplicate of Listing 13-9, except that the source of the XML data is the DCV_DB database created in Chapter 12.

Listing 13-13. Dumping the DCV_DB Database to a Console Using XML

using namespace

System;

using namespace

System::Data;

using namespace

System::Data::SqlClient;

using namespace

System::Xml;

String ^indent(int depth)

{

 

String ^ind

= "";

return ind->PadLeft(depth*4, ' ');

}

void Navigate(XmlNode ^node, int depth)

{

if (node == nullptr) return;

602 C H A P T E R 1 3 X M L

Console::WriteLine("{0}: Name='{1}' Value='{2}'", String::Concat(indent(depth),node->NodeType.ToString()), node->Name, node->Value);

if (node->Attributes != nullptr)

{

for (int i = 0; i < node->Attributes->Count; i++)

{

Console::WriteLine("{0}Attribute: Name='{1}' Value='{2}'", indent(depth+1),node->Attributes[i]->Name, node->Attributes[i]->Value);

}

}

Navigate(node->FirstChild, depth+1); Navigate(node->NextSibling, depth);

}

void main()

{

XmlDocument ^doc = gcnew XmlDocument();

try

{

SqlConnection ^connect = gcnew SqlConnection();

#ifdef SQLAuth

 

//

SQL Server authentication

connect->ConnectionString =

 

"User ID=sa; Password=;"

 

"Data Source=(local); Initial Catalog=DCV_DB;";

#else

 

 

//

Windows Integrated Security

connect->ConnectionString =

 

"Persist Security Info=False; Integrated Security=SSPI;"

 

"Data Source=(local); Initial Catalog=DCV_DB;";

#endif

 

 

SqlDataAdapter ^dAdapt = gcnew SqlDataAdapter();

DataSet ^dSet

= gcnew DataSet();

dAdapt->SelectCommand

=

gcnew SqlCommand("SELECT * FROM Authors", connect);

dAdapt->Fill(dSet, "Authors");

XmlDataDocument ^doc = gcnew XmlDataDocument(dSet);

// Recursive navigation of the DOM tree Navigate(doc->DocumentElement, 0);

}

catch (Exception ^e)

{

Console::WriteLine("Error Occurred: {0}", e->Message);

}

}

C H A P T E R 1 3 X M L

603

As you can see, the only code that is different from the original (Listing 13-9) is the standard code to create a DataSet and then the placing of the DataSet within an XmlDataDocument. If you need a refresher on creating a DataSet, please review Chapter 12.

SqlConnection ^connect = gcnew SqlConnection();

#ifdef SQLAuth

// SQL Server authentication connect->ConnectionString =

"User ID=sa; Password=;"

"Data Source=(local); Initial Catalog=DCV_DB;";

#else

// Windows Integrated Security connect->ConnectionString =

"Persist Security Info=False; Integrated Security=SSPI;" "Data Source=(local); Initial Catalog=DCV_DB;";

#endif

SqlDataAdapter ^dAdapt = gcnew SqlDataAdapter(); DataSet ^dSet = gcnew DataSet();

dAdapt->SelectCommand = gcnew SqlCommand("SELECT * FROM Authors", connect);

dAdapt->Fill(dSet, "Authors");

XmlDataDocument ^doc = gcnew XmlDataDocument(dSet);

Figure 13-13 shows the resulting console dump by ADONET.exe of all the nodes and attributes that make up the DCV_DB database DOM tree.

Figure 13-13. The console dump of the DCV_DB database DOM tree

Summary

In this chapter you covered the last of the .NET Framework class library’s standard I/O mechanisms. You started with a quick refresher on XML. You then learned how to read, validate, write, and update XML documents using forward-only access. Then you looked at DOM trees and how to go about

604 C H A P T E R 1 3 X M L

reading from, updating, and writing to them. Next, you took a brief look at the powerful XPath. You finished off by learning how simple it is to manipulate ADO.NET databases using XML.

Now with all four I/O systems covered, you should have no problems getting the necessary information into your system.

In the next chapter, you will explore the first of two service types covered in this book, the Windows service.

C H A P T E R 1 4

■ ■ ■

Windows Services

The .NET Framework provides two considerably different types of services applications to the developer: the Windows service, which I cover in this chapter, and the Web service, which I cover in the next chapter. Although both are called services, they are very different. Windows services are standalone installed applications, while, as you shall see, Web services provide a service via a network to another application.

Windows services, I’d like to point out, is a bit of a misnomer, as this same functionality is also available on the Mono/Linux platform using what is called a monod, which (I believe) is an implementation of a forked daemon and has nothing to do with Windows at all. I am also pretty sure the other .NET-implemented platforms don’t use Windows in any way to implement the functionality. Admittedly, I have not looked into it. Personally, I think Windows services should be called service processes, as the implementing .NET Framework namespace suggests.

That being said, so as to not confuse the Windows developer, this chapter will focus on the Windows implementation of the service process and use the term Windows service. This kind of makes sense, as C++/CLI currently only has (as far as I know) a Windows implementation. Hopefully, since Microsoft has released the standard to the ECMA, there will be other implementations on other platforms.

Note Windows Services do not run on Windows 98 or Windows ME. They require the NT kernel and thus run on Windows NT4, Windows 2000, Windows XP, and Windows 2003.

This chapter starts out by providing you with a general understanding of Windows services and its three parts: the service application, the service control application, and the service configuration application. Next, you will see how to create, install, and uninstall a Windows service. Then you will take a look at how to manage a Windows service. Finally, you will take a look at how to debug the Windows service, as it is done a little differently from the normal debugging process.

What Are Windows Services?

A Windows service, or what used to be known as an NT service, is an installed long-running executable application that runs in its own process space. Windows services can run without a user context (albeit only under Windows NT, Windows 2000, or Windows XP and Windows Vista at this point).

They don’t require a user to be logged in to function, and they generally run in a higher-powered security mode than do most users. A Windows service is normally automatically started when the computer boots, but it can be also started, paused, restarted, and stopped manually by a user.

Another telling aspect of Windows services is that it has no user interface, thus making it good for the scenario where the user needs some long-running functionality that does not interfere with

605

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

users working on the computer. Also, due to the fact that the Windows service has no interface, it is ideal for running in the background thread on a server. Since I do not cover multithreading until later in the book (Chapter 16), I will not the cover placing of a Windows service in a background thread, but after you have read Chapter 16, you should have little difficulty doing so.

Note Not having an interface, though, does not make an application a service. Console applications can be written without an interface as well. Typically, services provide system-level support, including a system event log, performance counters, and a task scheduler, but again that does not make an application a service either.

As mentioned previously, a Windows service is installed into the registry as an executable object. As you will see, the main() method does not actually run the service; instead, it is used to install the service into the registry. To actually start a Windows service, you will need to use either the Services application, which is part of the Administrative Tools on the Control Panel (see Figure 14-1), or create your own service control application. (You can also configure your Windows service to automatically start at startup as well.)

Figure 14-1. The Administrative Tools’ Services application

In case you are interested, all Windows services installed on a computer can be found in the registry at

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

There are several different types of Windows services that can be created, but only two managed code types can be created with the .NET Framework. A Windows service made up of only a single service in a process is of type Win32OwnProcess, while a Windows service made up of multiple services in a single shared process is of type Win32ShareProcess.

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

607

You can find out the type of Windows service you are accessing by querying the property ServiceController.ServiceType. If the service was not created by .NET, then it is possible for this property to have other values as listed in Table 14-1.

Table 14-1. Windows Service Types

ServiceType

Description

Adapter

A service for a piece of hardware that needs its own driver.

FileSystemDriver

A file system driver. This is a specific type of kernel driver.

InteractiveProcess

A service that can communicate with the desktop.

KernelDriver

A low-level hardware device driver.

RecognizerDriver

A file system driver used during the system startup to determine file

 

system types.

Win32OwnProcess

A service made up of only a single service in a process.

Win32ShareProcess

A service made up of multiple services in a single shared process.

 

 

Architecture of Windows Services

Unlike other application types, Windows services actually require three different programs to function properly. The first program is the service application itself. This program implements the functionality required by the Windows service. The second program is the service control application. This program provides the ability to start, pause, restart, stop, and send unique commands to the service application. The final program is the service configuration application. This program installs and configures the service application.

Service Application

The service application provides the functionality of the Windows service. But since it is a registry executable object, it is internally set up a little differently from other applications. The service application is also made up of three parts: the main, the service-main, and the handlers.

The main part provides the ability to register the true entry point or points of the service application, the service-main or service-mains. This dual functionality is required because a Windows service can be either of type Win32OwnProcess or Win32ShareProcess. Thus, when the Windows service is of type Win32OwnProcess, the main part must register the single service-main that makes up the Windows service. On the other hand, when the Windows service type is Win32ShareProcess, then the main part must register the multiple services that comprise the Windows service.

The service-main is the Windows service’s interface to the outside world and is called when the service needs to be started. Once called, the service-main then needs to register a handler to the Service Control Manager (SCM).

The SCM is part of the operating system that communicates with the Windows service. It is the SCM that sends events to the third part of the service application, the handler. It is up to the handler to handle the start, pause, continue, stop, and custom events sent to it from the SCM.