What Is Reflection?


Reflection is the process of runtime discovery of data types. It allows you to load an assembly, examine the manifest, and discover all types residing within the assembly. This includes external assembly references, methods, fields, properties, and events defined by a specified type. Reflection also allows you to examine interfaces supported by a given class, a method’s parameters, namespaces, and base classes. ILDasm.exe, a tool supplied by the Framework, allows you to inspect an assembly at design time.

To understand reflection, familiarize yourself with the Type class defined in the System.Type namespace as well as System.Reflection. (The latter namespace facilitates late binding and permits you to load assemblies at runtime.) Table 5-1 provides a partial listing of the Type class.

Table 5-1: Type Class (Partial Listing)

Type Member

Definition

IsAbstract
IsArray
IsClass
IsCOMObject
IsEnum
IsInterface
IsSealed
IsValueType

These properties allow you to discover traits about the Type, for example, an array, a nested class, an abstract type, etc.

GetConstructors()
GetEvents()
GetFields()
GetInterfaces()
GetMethods()
GetMembers()
GetProperties()

These methods facilitate access to arrays representing the items, namely, interfaces, methods, properties, etc. Each method returns a specified array. You can retrieve a single item by specifying the name, rather than an array of all items.

FindMembers()

This method returns an array of MemberInfo types.

GetType()

This method returns a Type instance.

InvokeMembers()

This method facilitates late binding for a specified item.

The System.Type Namespace

Let’s examine the System.Type namespace. System.Object defines a method called GetType() and returns a valid instance of the Type class.

Note

Type is an abstract class; therefore, you cannot create an instance using new. There is, however, a method for achieving this. Observe the following fragments of code.

// Apply a valid Author instance
Author myAuthor = new Author();
Type t = myAuthor.GetType();

There is an alternate method:

// Use the static Type.GetType()method
Type t = null;
t = Type.GetType('Author');

You can employ still another approach:

// use the Typeof keyword
Type t = Typeof(Author);

Creating a Class Library

Now, as an example, let’s build a class library named “Customer.” Open Visual Studio .NET and create a project in Visual Basic .NET called Customer.

The class library design begins by creating the project in Visual Studio .NET. Select the class library icon. In VB .NET, the module has a .vb extension and a single source code file. It can contain more than one class. In the code window, change the class definition line to class Customer, as shown here:

Public Class Class1
to the following:
Public class Customer

In the Solution Explorer, right-click on the class1.vb icon and select Rename. Change the name to Customer.vb. Notice that the class does not have a constructor (Public SubNew) or a code designer; there is a way of creating both. Right-click on the project name and choose Add, and then select Add Component. This adds a new file to the class. The Component class allows a developer to drag and drop data components to the designer.

start sidebar
Examining Visual Studio .NET

Visual Studio .NET is an Integrated Development Environment (IDE), allowing developers to leverage all .NET tools, profilers, cross-language debugging, and error handling within a single environment. You can also develop nonmanaged code (also referred to as native code) and work with traditional COM objects within Visual Studio .NET. This means you write your code with types not supported by the Framework. However, developers are not obligated to develop their code to receive full support from the Framework. Alternatively, you can use your favorite text editor and compile your applications with command line compilers provided by Microsoft for each .NET-hosted language. It is more convenient, however, to develop applications within the IDE because it is user friendly. You can accomplish all programming tasks within the IDE by using the profilers, debuggers, and tools without leaving the IDE.

end sidebar

It is time to add some code to the Customer class. Add a first name property by initially declaring a variable, as shown here:

Public Class Customer
Dim msFirstName As String
Public Property FirstName() As String
Get
Return msFirstName
End Get
Set(ByVal Value As String)
msFirstName = Value
End Set
End Property
End Class

Compile the Customer project. If everything compiles, build a client and the library’s services. From the File menu, select a new project and highlight the Windows application icon. Name the application CustomerClient and select the radio button to add the solution to the current project. Then click OK. In Solution Explorer, right-click the CustomerClient icon and select Set as Startup Project.

The next step is critical because you must have a reference to the Customer DLL. Select the CustomerClient icon and choose Add Reference. In the dialog box that opens, select Project from the tab and browse to the Customer project. The Customer DLL has been placed in the Selected Components box. Select the DLL and click on it to add a reference. Now, proceed to the form and drag a button from the toolbox onto the form. Double-click on the button to access the following code that Visual Studio has generated for you:

Private Sub Button1_Click(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles Button1.Click

Then, enter the following code and declare the variable first:

Dim MyNewCustomer As New Customer.Customer
MyNewCustomer.FirstName = "Steve"
MsgBox(MyNewCustomer.FirstName)
End Sub
End Class

Finally, a DLL has been created so it is now possible to examine the PE file that contains the program code. The tool used is dumpbin.exe. The command line format is dumpbin.exe filename, and its type, EXE or DLL.

Reading Metadata

The PE file structure and its numerous header segments can be displayed with

dumpbin.exe CustomerClient.dll /all

Much of the file is left out of the following to highlight only the basic features of its structure:

Microsoft <R> COFF/PE Dumper Version 7.10 3077
Copyright <C> Microsoft Corporation. All rights reserved.
Dump of file Customer.dll
PE signature found
File Type: DLL
File Header values [MS-DOS/COFF HEADERS]
14C machine (x86)
OPTIONAL HEADER VALUES
10B magic #(PE32)
Section Header #1 [SECTION DATA]
...
.text name

Code Execute Read
RAW DATA #1
...
clr Header
//Section contains the following imports:
Mscoree.dll
402000 Import Address Table
4025F8 Import Name Table
...
0_CorDLLMain

The PE file lists the MS-DOS and Common Object File Format (COFF) headers. The overall file structure is the same for all Windows files. Microsoft has extended the PE file by adding headers to accommodate the Framework MSIL binaries.

Continuing through the file, notice that it supports 32-bit Windows programs. The “File Header values” segment indicates there are three sections in this file. “Section Header #1” stores the CLR header and data. The next segment, “Code Execute Read,” informs the OS loader that the CLR contains code to be executed by the CLR at runtime. The rest of the PE file holds individual segments for .rdata, .rscr, and .text, as demonstrated before in the header file. When the CLR searches and locates the header, it executes 0_CorDLLMain.

Note

The CLR header and data section contain both metadata and IL code. These describe in detail how CustomerClient executes. MSIL code is similar to Java’s bytecode. If the file is an executable, it executes 0_CorEXEMain.

The PE file hosts the application code, whereas the manifest (recall that it contains metadata about the assembly) describes all references to external libraries, methods, classes, and types in binary format. Table 5-2 contains a list of manifest tags. As mentioned earlier, Framework provides an external tool, ILDasm.exe, to examine the metadata.

Table 5-2: Manifest Intermediate Language Tags

Tag Attribute

Meaning

.assembly

Denotes an assembly declaration.

.file

Indicates files residing within the assembly.

.assembly extern

Informs you that there is an external reference to an assembly residing elsewhere on the Web or other network. This assembly is essential to application execution.

.class extern

Indicates classes exported by the assembly. However, they are declared in another assembly.

.exeloc

Provides information about a location for an executable.

.module

Denotes a .NET module.

.module extern

Indicates that other modules contained within this module have items referenced within the current module.

.publickey

Represents the public key bytes.

.publickeytoken

Contains a token of the public key.

At the Visual Studio command prompt, navigate to the directory where your code lies. Then, type ildasm.exe to display the manifest and the assembly. This file references an external assembly, mscorlib. As previously mentioned, this core base-class library represents all classes providing support for the CLR. It identifies the version as 1.0 and a publickeytoken as (B7 7A 5C 56 19 34 E0 89). It also references the System.Windows.Forms. The assembly contains the binaries and offers support for MessageBox.Show. In addition, the manifest lists the current assembly, CustomerClient. Continuing on, mscorlib employs the System.Reflection namespace and its constructors. The manifest also displays a hash algorithm value and numerous other properties.

Now, let’s examine the assembly itself. The following lists tree icons with their meanings and a description:

Icon

Description

Blue shield

Namespace

Blue rectangle with three outputs

Class

Blue rectangle with three outputs marked ‘I’

Interface

Brown rectangle with three outputs

Value class

Brown rectangle with three outputs marked ‘E’

Enum

Magenta rectangle

Method

Magenta rectangle marked ‘S’

Static method

Cyan diamond

Field

Cyan diamond marked ‘S’

Static field

Green point-down rectangle

Event

Red point-up triangle

Property

Red point-right triangle

Manifest or a class info item

Understanding and Building Dynamic Assemblies

There are two kinds of assemblies, static assemblies and dynamic assemblies. Static assemblies are stored somewhere on your hard drive, whereas a dynamic assembly is created by System.Reflection.Emit on the fly. System.Reflection.Emit creates a dynamic assembly in memory at runtime. After creating your assembly, store it at runtime on the hard drive. Once the assembly exists, it changes back to its static status. Furthermore, it is possible to add new types dynamically to the runtime assembly. For example, web-enabled languages such as JScript.NET generate raw code, emit IL code, and store it dynamically in an assembly. At this point, examine the types defined within the System.Reflection.Emit namespace. Table 5-3 displays several Types from this namespace.

Table 5-3: System.Reflection.Emit Types

System.Reflection.Emit

Namespace Definition

AssemblyBuilder

AssemblyBuilder creates an assembly at runtime. You can use it to create either an executable or DLL binary assembly. Executables should call the ModuleBuilder.SetEntryPoint() method to set the entry point for the module. If no entry point exists, AssemblyBuilder generates a DLL.

ModuleBuilder

Use ModuleBuilder to build a module within the assembly at runtime.

EnumBuilder

EnumBuilder generates a Type, for example, class, interface, etc., within an assembly.

TypeBuilder

TypeBuilder constructs a Type within a module at runtime.

MethodBuilder
EventBuilder
LocalBuilder
PropertyBuilder
FieldBuilder
ConstructorBuilder
CustomAttributeBuilder
ILGenerator

These items create a defined member of a Type, for example, methods, local variables, properties, attributes, and constructors at runtime.



ILGenerator generates intermediate language (IL) for a specific member at runtime.

International Finance Corporation Exchange: Building a Dynamic Assembly

Using our case study, let’s build a dynamic assembly called MyIFCEAssembly, and explore the methods listed in Table 5-3. The application, written in C#, creates a class at runtime named IFCE.

Public class IFCE
{
private string Msg;
//public interface to the class
IFCE(string s) {Msg = s;}
Public string GetMsg() {return Msg;}
Public void Greeter() {System.Console.WriteLine
("You are now a valid Customer at IFCE!");
Here is the application followed by comments.
namespace DynamicAssembly
{
using System;
using System.Reflection.Emit;
using System.Reflection;

//build the assembly dynamically
public class MyAssemblyBuilder
{
public int CreateMyAssembly(AppDomain cAppDomain)

//create assembly name and version
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "MyIFCEAssembly";
assemblyName.Version = new Version("1.0.0.0");

//now create the assembly in memory
AssemblyBuilder assembly
= cAppDomain.DefineDynamicAssembly(assemblyName,
AssemblyBuilderAccess.Save);

ModuleBuilder module =
assembly.DefineDynamicModule
("MyIFCEAssembly", "MyIFCEAssembly.dll");
//Define public class IFCE
TypeBuilder IFCEClass = module.DefineType("MyIFCEAssembly.IFCE",
TypeAttributes.Public);

//Define a private String variable named "Msg"
//private string Msg
FieldBuilder msgField = IFCEClass.DefineField("Msg",
Type.GetType("System.String"),
FieldAttributes.Private);

//create a constructor
Type[] constructorArgs = new Type[1];
constructorArgs[0] = Type.GetType("System.String");
ConstructorBuilder constructor =

IFCEClass.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard,constructorArgs);

ILGenerator constructorIL = constructor.GetILGenerator();
constructorIL.Emit(OpCodes.Ldarg_0);
Type objectClass = Type.GetType("System.Object");
ConstructorInfo superConstructor =
objectClass.GetConstructor(new Type[0]);
constructorIL.Emit(OpCodes.Call, superConstructor);
constructorIL.Emit(OpCodes.Ldarg_0);

constructorIL.Emit(OpCodes.Ldarg_1);
constructorIL.Emit(OpCodes.Stfld,msgField);
constructorIL.Emit(OpCodes.Ret);

//create GetMsg() method
MethodBuilder getMsgMethod =

IFCEClass.DefineMethod("GetMsg",MethodAttributes.Public,
Type.GetType("System.String"), null);

ILGenerator methodIL = getMsgMethod.GetILGenerator();
methodIL.Emit(OpCodes.Ldarg_0);
methodIL.Emit(OpCodes.Ldfld,msgField);
methodIL.Emit(OpCodes.Ret);

//create the Customer method
//public void InformCustomer()
MethodBuilder GreetingMethod =
IFCEClass.DefineMethod("InformCustomer",
MethodAttributes.Public, null, null);
methodIL = GreetingMethod.GetILGenerator();
methodIL.EmitWriteLine("Greetings, new Customer");
methodIL.Emit(OpCodes.Ret);
IFCEClass.CreateType();
//save the assembly to your hard drive
assembly.Save("MyIFCEAssembly.dll");
return 0;
}
}
}

Generate a single file assembly called MyIFCEAssembly, and name the class MyAssemblyBuilder. Then, build the dynamic assembly in memory and store it on the hard drive. Use AssemblyBuilder.DefineAssembly to create the assembly at runtime and save it with AssemblyBuilderAccess.Save. Next, construct the module bearing the same name as the assembly, MyIFCEAssembly.dll.

Next, TypeBuilder creates the class IFCEClass at runtime and declares TypeAttributes as public in scope. The next step requires a private string member variable named “Msg”. For this purpose, apply the FieldBuilder method and declare the variable. Now, write the following constructor

(IFCE(String s));

then, make use of the IlGenerator to create the underlying intermediate language (IL) for a class member at runtime.

After generating member variable Msg, you must create a method to get the message System.String by applying DefineMethod(“GetMsg”) and make the Method Attributes public.Next, write the InformCustomer method with the IFCE DefineMethod (“InformCustomer”).

When a customer initiates a purchase transaction for foreign currency, IFCE attempts to validate the customer and ascertain whether he or she has an existing account. Depending on the status of the customer (valid or nonvalid), several options exist:

  • A valid customer can pay with cash, credit card, or by check.

  • A nonvalid customer pays with cash.

The InformCustomer method determines the status and follows the normal procedures for the customer determination.

It is now appropriate to generate the actual class type assembly and save it to the disk. The runtime-generated assembly status is now static.

Next, create a client application and consume the IFCE service by adding a new class to the Dynamic Assembly namespace. Name the public class AssemblyReader. Here is the code:

namespace DynamicAssembly
{
using System;
using System.Reflection.Emit;
using System.Reflection;
using System.Threading;
public class AssemblyReader
{
public static int Main(string[] args)
{
AppDomain cAppDomain = Thread.GetDomain();
//create the dynamic assembly
MyAssemblyBuilder asmb = new MyAssemblyBuilder();
asmb.CreateMyAssembly(cAppDomain);
//load the assembly
Assembly a = null;
try
{
a = Assembly.Load("MyIFCEAssembly");
}
catch
{
Console.WriteLine(" can't locate the assembly ");
}
//get the Greeter Type
Type Greeter = a.GetType("MyIFCEAssembly.IFCE");
//create IFCE object
object[]ctorArgs = new object[1];
ctorArgs[0] = "Welcome, Mr. Hall,
you can now pay by cash,credit card, or by check!";
object obj = Activator.CreateInstance(Greeter,ctorArgs);
mi.Invoke(obj, null);
mi = Greeter.GetMethod("GetMsg");
Console.WriteLine(mi.Invoke(obj, null));
Console.ReadLine();
return 0;
}
}
}

Declare a new class named AssemblyReader. This class consumes the IFCE class services. You can call the appropriate domain utilizing the method Thread.GetDomain(). Then, compile MyIFCEAssembly. Now, load the assembly. Next, create the IFCE object and an instance of it. The Activator method invokes GetMessage and displays the message.

Now that a dynamic assembly has been generated at runtime, you can understand how significantly System.Reflection.Emit contributes to creating static assemblies. There can also be multifile static assemblies as well as shared assemblies. Before making MyIFCEAssembly a shared, global assembly, you must first create a strong name. It is now important to come to terms with two separate specifications:

  • Common Type Specification (CTS)

  • Common Language Specification (CLS)

We’ll explore the Common Type Specification first.




.NET & J2EE Interoperability
Microsoft .NET and J2EE Interoperability Toolkit (Pro-Developer)
ISBN: 0735619220
EAN: 2147483647
Year: 2004
Pages: 101
Authors: Simon Guest

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net