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

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

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

728 C H A P T E R 1 7 N E T W O R K P R O G R A M M I N G

Figure 17-5. The asynchronous TCP server in action

Summary

In this chapter you got a fairly high level look at the weighty topic of network programming. I started out looking at connection-oriented client-server sockets, in particular a TCP server and client. I then looked at connectionless client-server sockets or, more specifically, a UDP server and client. Next, I looked at some of the helper functions provided by the .NET Framework to simplify network programming. Finally, I covered asynchronous network programming.

In the next chapter of this book, you’ll cover assembly programming and how you can augment your assemblies with resources, localization, attributes, and type reflection.

C H A P T E R 1 8

■ ■ ■

Assembly Programming

Before you roll your eyes and mumble under your breath, “Not another chapter on assemblies,” read the chapter title again. This chapter is about programming an assembly, and not about the assembly. By now I’m assuming you know what an assembly is, its structure, how it eliminates DLL Hell, and so forth. Instead, this chapter focuses on programmatically playing with the assembly.

As I’ve pointed out a few times in this book, the assembly is the cornerstone of .NET Framework deployment. To paraphrase, all roads lead to the assembly. Because this is the case, it only makes sense that the .NET Framework provides the programmer many programmatic tools to interact directly with the assembly.

In this chapter, you’ll look at some of these programming tools. Most of these tools are for the more advanced C++/CLI programmer. In most cases, you won’t have to use them for most of your programs. On the other hand, knowing these tools will provide you with powerful weapons in your

.NET software development arsenal and, inevitably, sometime in your coding career you’ll need to use each of these tools.

The first tool, reflection, gives you the ability to look inside an assembly to see how it works. You’ve used system-defined attributes on several occasions in this book. In this chapter you’ll have the opportunity to create some of your own attributes. Up until now, you’ve worked only with private assemblies, but it’s also possible to share them. Most of the time, you’ll take versioning (the second tool) for granted, but you can take a much more active role. Assemblies need not be just metadata and MSIL code. They can house almost any resource that your program needs to run. The last tool—but definitely not the least—is globalization and localization. Your culture may be central to your life, but there are many other cultures out there. Why not make your programs work with these cultures as well?

Reflection

Reflection is the ability to retrieve and examine at runtime the metadata that describes the contents of assemblies, i.e., classes, enums, methods, variables, etc. Then, for example, using the retrieved information, you can turn around and create dynamically an instance of one of these described classes, and invoke its methods or access its properties or member variables.

The System::Reflection namespace, which the .NET Framework uses to support reflection, is made up of more than 40 classes. Most of these classes you will probably not use directly, if at all. Several of the more common classes you will use are listed in Table 18-1.

729

730 C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

Table 18-1. Common System::Reflection Namespace Classes

Class Name

Description

Assembly

Defines an assembly

AssemblyName

Provides access to all the parts of an assembly’s name

ConstructorInfo

Provides access to the constructor’s attributes and metadata

CustomAttributeData

Provides access to custom attribute data for assemblies, modules,

 

types, members, and parameters

EventInfo

Provides access to the event’s attributes and metadata

FieldInfo

Provides access to the field’s attributes and metadata

MemberInfo

Provides access to the member’s attributes and metadata

MethodInfo

Provides access to the method’s attributes and metadata

Module

Defines a module

ParameterInfo

Provides access to the parameter’s attributes and metadata

PropertyInfo

Provides access to the property’s attributes and metadata

TypeDelegator

Provides a wrapper for an object and then delegates all methods to

 

that object

 

 

Just to make things a little confusing, the key to .NET Framework reflection is the System::Type class which, as you can see, isn’t even found within the Reflection namespace. My guess for its not being placed in the Reflection namespace is because it’s used frequently, and the designers of the Framework didn’t want to force the import of the Reflection namespace.

Examining Objects

A key feature of reflection is the ability to examine metadata using the System::Type class. The basic idea is to get a Type reference of the class you want to examine and then use the Type class’s members to get access to the metadata information about the type, such as the constructors, methods, fields, and properties.

Getting the Type Reference

In most cases, you will get the Type reference to the class by one of four methods:

Using the typeid keyword

Calling the class’s GetType() method

Calling the Type class’s static GetType() method, passing it the name of the class to be examined as a String

Iterating through a collection of all types within an assembly retrieved by the Assembly class’s

GetTypes() method

C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

731

The first method, typeid, is the easiest of the four ways to get a Type reference, but it must be able to be evaluated at compile time. The typeid keyword returns a Type of the specified type:

System::Type ^myClassType = MyClass::typeid;

To use the second method, you need to already have an instance of the managed object you want to examine, and with this instance you call its GetType() method. The key to the second method is the fact that all ref classes and value types inherit from the Object class and the Object class has a GetType() method. For example, here is how you would get the Type reference to the myClass class:

ref class myClass

{

// members

};

MyClass ^myClass = gcnew MyClass(); Type ^myClassType = myClass->GetType();

The third method is kind of cool in that you pass the string equivalent of the type you want to reference to the Type class’s static GetType() method. You might want to note that Type is an abstract class, so you can’t create an instance of it but, as you can see here, you can still call its static methods:

Type ^myClassRef = Type::GetType("MyClass");

Tip Since Type::GetType() takes a string as a parameter, you can use this function to create nearly any type you want at runtime. In fact, in my current project, I use a database of class names (the actual classes all derived from a common interface) to populate this method. Then I use the polymorphic abilities of C++/CLI to provide the appropriate functionality of the class selected from the database.

One thing all the preceding methods have in common is that you need to have something of the type you want to reference at runtime—either the data type and instance of the type, or the name of the type. The fourth method allows you to get a Type reference without any knowledge of the object beforehand. Instead, you retrieve it out of a collection of Types with an assembly:

Assembly^ assembly = Assembly::LoadFrom("MyAssembly.dll"); array<Type^>^ types = assembly->GetTypes();

for each (Type ^type in types)

{

Type ^myClassType = type;

}

Getting the Metadata

Getting the metadata out of a Type reference is the same no matter what method you use to attain the Type reference. The Type class contains numerous methods, many of which allow you to access metadata associated with the type. Table 18-2 lists of some of the more common methods available to you for retrieving metadata.

732 C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

Table 18-2. Common Type Metadata Retrieval Methods

Method

Description

GetConstructor()

Gets a ConstructorInfo object for a specific constructor of the

 

current Type

GetConstructors()

Gets a collection of ConstructorInfo objects for all the constructors for

 

the current Type

GetEvent()

Gets an EventInfo object for a specific event declared or inherited from

 

the current Type

GetEvents()

Gets a collection of EventInfo objects for all the events declared or

 

inherited from the current Type

GetField()

Gets a FieldInfo object for a specific member variable from the

 

current Type

GetFields()

Gets a collection of FieldInfo objects for all the member variables from

 

the current Type

GetInterface()

Gets a Type object for a specific interface implemented or inherited

 

from the current Type

GetInterfaces()

Gets a collection of Type objects for all the interfaces implemented or

 

inherited from the current Type

GetMember()

Gets a MemberInfo object for a specific member from the current Type

GetMembers()

Gets a collection of MemberInfo objects for all the members from the

 

current Type

GetMethod()

Gets a MethodInfo object for a specific member method from the

 

current Type

GetMethods()

Gets a collection of MethodInfo objects for all the member methods

 

from the current Type

GetProperty()

Gets a PropertyInfo object for a specific property from the current Type

GetProperties()

Gets a collection of PropertyInfo objects for all the properties from the

 

current Type

 

 

Along with the “Get” methods, the Type class also has a number of “Is” properties (see Table 18-3), which you use to see if the current type “is” something.

Table 18-3. Common “Is” Properties

“Is” Property

Description

IsAbstract

Is a Boolean that represents whether the Type is abstract

IsArray

Is a Boolean that represents whether the Type is a managed array

IsClass

Is a Boolean that represents whether the Type is a ref class

IsEnum

Is a Boolean that represents whether the Type is an enumeration

IsImport

Is a Boolean that represents whether the Type is an interface

C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

733

Table 18-3. Common “Is” Properties

“Is” Property

Description

IsNotPublic

Is a Boolean that represents whether the Type is not public

IsPrimitive

Is a Boolean that represents whether the Type is a .NET primitive (Int32,

 

Single, Char, and so on)

IsPublic

Is a Boolean that represents whether the Type is public

IsSealed

Is a Boolean that represents whether the Type is sealed

IsSerializable

Is a Boolean that represents whether the Type is serializable

IsValueType

Is a Boolean that represents whether the Type is a value type

 

 

Listing 18-1 shows how to build a handy little tool that displays the member methods, properties, and variables of the classes found in the six most commonly referenced assemblies in the .NET Framework using reflection.

Note To save space and because it isn’t directly relevant, all the code examples in this chapter don’t include the autogenerated Windows Form GUI code. (See Chapters 9 and 10 for more information on Windows Form development.)

Listing 18-1. Referencing the Class Members of the .NET Framework

namespace Reflecting

{

//...Standard Usings

using namespace System::Reflection;

public ref class Form1 : public System::Windows::Forms::Form

{

//...Auto-generated GUI interface code

private: array<Type^>^ types;

private: static array<String^>^ assemblies =

{

"System",

"System.Drawing",

"System.Xml",

"System.Windows.Forms",

"System.Data",

"mscorlib"

};

private:

System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e)

{

734 C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

for each (String ^assembly in assemblies)

{

cbAssemblies->Items->Add(assembly);

}

cbAssemblies->SelectedIndex = 0;

}

private:

System::Void cbAssemblies_SelectedIndexChanged(System::Object^ sender, System::EventArgs^ e)

{

Assembly^ assembly = Assembly::LoadWithPartialName( assemblies[cbAssemblies->SelectedIndex]);

types = assembly->GetTypes();

cbDataTypes->Items->Clear();

for (int i = 0; i < types->Length; i++)

{

cbDataTypes->Items->Add(types[i]->ToString());

}

cbDataTypes->SelectedIndex = 0;

}

private:

System::Void cbDataTypes_SelectedIndexChanged(System::Object^ sender, System::EventArgs^ e)

{

Type ^type = types[cbDataTypes->SelectedIndex];

array <MemberInfo^>^ methods = type->GetMethods(); lbMethods->Items->Clear();

for (int i = 0; i < methods->Length; i++)

{

lbMethods->Items->Add(methods[i]->ToString());

}

array <PropertyInfo^>^ properties = type->GetProperties(); lbProperties->Items->Clear();

for (int i = 0; i < properties->Length; i++)

{

lbProperties->Items->Add(properties[i]->ToString());

}

array <MemberInfo^>^ variables = type->GetFields(); lbVariables->Items->Clear();

for (int i = 0; i < variables->Length; i++)

{

lbVariables->Items->Add(variables[i]->ToString());

}

}

};

}

C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

735

As you can see from the code in the preceding example, reflection can be fairly easy to work with. Simply “Get” the metadata needed and then loop through the metadata. Admittedly, the example is not the most elaborate, but it still shows the potential power it has in making the metadata information within an assembly available.

Most of the preceding code is simply to load the appropriate GUI controls, but one thing new in the preceding example that hasn’t been covered before is the use of the System::Reflection::Assembly class. The Assembly class is a core building block of all .NET Framework applications, though normally, even as a .NET developer, you seldom have to know of its existence.

When it comes to reflection, the Assembly class contains the starting point for retrieving any public metadata information you want about the current active assembly or one that you load using one of the many different loading methods. The only reason I see that there are multiple load methods (each has multiple overload) is due to the duplicated method signature required to support the myriad ways available to load an assembly. Essentially, all load methods do the same thing—load the assembly—with the only differences relating to the amount of information known about the assembly being loaded and the source of the assembly.

The LoadWithPartialName() method requires the least amount of information—simply the name of an assembly. It does not care about version, culture, and so on. It is also the method that the .NET Framework frowns upon using for that exact reason. In fact, Microsoft has gone and made it obsolete in version 2 of the .NET Framework. But in the case of this example, it works just fine.

Figure 18-1 shows Reflecting.exe in action. As you can see, it’s made up of two ComboBoxes and three ListBoxes. The first ComboBox provides a way of selecting the assembly, and the second allows you to select the type. The results of these two selections are the methods, properties, and variables displayed in the ListBoxes.

Figure 18-1. The Reflecting program in action

Dynamically Invoking or Late-Binding Objects

Reflection provides you with the rather powerful feature known as late binding.Late binding is the ability for different methods or objects to be invoked at runtime. These methods and objects are not statically known at compile time.

A cool thing about reflection is that once you have a reference to the method you want to invoke (which I showed how to do previously), it is not a large step to execute that method in a dynamic

736 C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

fashion. In fact, all you have to do is invoke the method using the (you guessed it) MethodInfo::Invoke() method.

The trickiest part of invoking methods using reflection is realizing that there are two types of methods: static and instance. Static methods are the easiest to handle, as you don’t need to create an instance of the method’s class to invoke it. Simply find the Method reference type and then use the

Invoke() method:

MethodInfo ^method = type->GetMethod(); method->Invoke(nullptr, nullptr);

Notice that in the preceding example the Invoke() method has two parameters. The first is the instance of the class for which you are invoking the method. The second is an array of parameters that will be passed to the method. As you can now tell, the preceding example is not only a static method. It also takes no parameters.

If the method you want to invoke is an instance method, it is not quite as easy because you need to create an instance of the type for that method. The .NET Framework provides you help in the way of the System::Activator class, which contains the static CreateInstance() method to create objects:

Type ^type = assembly->GetType("MyType");

Object ^typeInstance = Activator::CreateInstance(type);

Now that you have an instance of the method class, all you have to do is pass it as the first parameter:

method->Invoke(typeInstance, nullptr);

To pass parameters to the Invoke() method, simply create an array of them and assign the array to the second parameter:

array<Object^>^ args = gcnew array<Object^>(2); args[0] = parameterOne;

args[1] = parameterTwo;

That’s really all there is to late binding.

Note This is a second Invoke() method, but I have yet to use it as it is much more involved. If you are interested, it can be found in the .NET Framework documentation.

Listing 18-2 shows how to execute both a static and an instance method using reflection. The first thing the example does is create an array using reflection of all the static color properties of the Color structure. It then displays the color as the background of a label by invoking the property’s getter method. Next, the example dynamically invokes a method from one of two different classes to display the color name in the label. (There are much easier ways to do this without reflection, obviously.)

Listing 18-2. Using Reflection to Change the Properties of a Label

namespace Invoking

{

//... Standard Usings

using namespace System::Reflection;

public ref class Form1 : public System::Windows::Forms::Form

{

// Auto-generated GUI Interface code

C H A P T E R 1 8 A S S E M B L Y P R O G R A M M I N G

737

private:

array <PropertyInfo^>^ colors;

private:

System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e)

{

Type^ colorType = Color::typeid; colors = colorType->GetProperties();

for (int i = 0; i < colors->Length; i++)

{

if (colors[i]->ToString()->IndexOf("System.Drawing.Color") >= 0) cbColor->Items->Add(colors[i]->ToString());

}

cbColor->SelectedIndex = 0;

}

System::Void comboBox1_SelectedIndexChanged(System::Object^ sender, System::EventArgs^ e)

{

static bool alternateWrite = true;

PropertyInfo ^ColorProp = colors[cbColor->SelectedIndex];

MethodInfo ^PropMethod = ColorProp->GetGetMethod();

lbColor->BackColor = (Color)PropMethod->Invoke(nullptr,nullptr);

Assembly ^assembly = Assembly::Load("Invoking");

Type ^type;

if (alternateWrite)

type = assembly->GetType("Invoking.Writer1");

else

type = assembly->GetType("Invoking.Writer2");

alternateWrite = !alternateWrite;

MethodInfo ^ColorMethod = type->GetMethod("aColor");

Object ^writerInst = Activator::CreateInstance(type);

array <Object^>^ args = gcnew array <Object^>(1); args[0] = PropMethod->Invoke(nullptr,nullptr);

lbColor->Text = (String^)ColorMethod->Invoke(writerInst, args);

}

};