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

Pro CSharp And The .NET 2.0 Platform (2005) [eng]

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

354 C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

At this point you (hopefully) have a better understanding about the internal composition of a .NET binary file. With this necessary preamble out of the way, we are ready to dig into the details of building and configuring a variety of code libraries.

Building and Consuming a Single-File Assembly

To begin the process of comprehending the world of .NET assemblies, you’ll first create a single-file *.dll assembly (named CarLibrary) that contains a small set of public types. To build a code library using Visual Studio 2005, simply select the Class Library project workspace (see Figure 11-5).

Figure 11-5. Creating a C# code library

The design of your automobile library begins with an abstract base class named Car that defines a number of protected data members exposed through custom properties. This class has a single abstract method named TurboBoost(), which makes use of a custom enumeration (EngineState) representing the current condition of the car’s engine:

using System;

namespace CarLibrary

{

// Represents the state of the engine. public enum EngineState

{engineAlive, engineDead }

//The abstract base class in the hierarchy. public abstract class Car

{

C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

355

protected string petName; protected short currSpeed; protected short maxSpeed;

protected EngineState egnState = EngineState.engineAlive;

public abstract void TurboBoost();

public Car(){}

public Car(string name, short max, short curr)

{

petName = name; maxSpeed = max; currSpeed = curr;

}

public string PetName

{

get { return petName; } set { petName = value; }

}

public short CurrSpeed

{

get { return currSpeed; } set { currSpeed = value; }

}

public short MaxSpeed

{get { return maxSpeed; } } public EngineState EngineState

{get { return egnState; } }

}

}

Now assume that you have two direct descendents of the Car type named MiniVan and SportsCar. Each overrides the abstract TurboBoost() method in an appropriate manner.

using System;

using System.Windows.Forms;

namespace CarLibrary

{

public class SportsCar : Car

{

public SportsCar(){ }

public SportsCar(string name, short max, short curr) : base (name, max, curr){ }

public override void TurboBoost()

{

MessageBox.Show("Ramming speed!", "Faster is better...");

}

}

public class MiniVan : Car

{

public MiniVan(){ }

public MiniVan(string name, short max, short curr) : base (name, max, curr){ }

356 C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

public override void TurboBoost()

{

// Minivans have poor turbo capabilities! egnState = EngineState.engineDead;

MessageBox.Show("Time to call AAA", "Your car is dead");

}

}

}

Notice how each subclass implements TurboBoost() using the MessageBox class, which is defined in the System.Windows.Forms.dll assembly. For your assembly to make use of the types defined within this external assembly, the CarLibrary project must set a reference to this binary via the Add Reference dialog box (see Figure 11-6), which you can access through the Visual Studio 2005 Project Add Reference menu selection.

Figure 11-6. Referencing external .NET assemblies begins here.

It is really important to understand that the assemblies displayed in the .NET tab of the Add Reference dialog box do not represent each and every assembly on your machine. The Add Reference dialog box will not display your custom assemblies, and it does not display all assemblies located in the GAC. Rather, this dialog box simply presents a list of common assemblies that Visual Studio 2005 is preprogrammed to display. When you are building applications that require the use of an assembly not listed within the Add Reference dialog box, you need to click the Browse tab to manually navigate to the *.dll or *.exe in question.

Note Although it is technically possible to have your custom assemblies appear in the Add Reference dialog box’s list by deploying a copy to C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PublicAssemblies, there is little benefit in doing so. The Recent tab keeps a running list of previously referenced assemblies.

C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

357

Exploring the Manifest

Before making use of CarLibrary.dll from a client application, let’s check out how the code library is composed under the hood. Assuming you have compiled this project, load CarLibrary.dll into ildasm.exe (see Figure 11-7).

Figure 11-7. CarLibrary.dll loaded into ildasm.exe

Now, open the manifest of CarLibrary.dll by double-clicking the MANIFEST icon. The first code block encountered in a manifest is used to specify all external assemblies that are required by the current assembly to function correctly. As you recall, CarLibrary.dll made use of types within mscorlib.dll and System.Windows.Forms.dll, both of which are listed in the manifest using the .assembly extern token:

.assembly extern mscorlib

{

.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )

.ver 2:0:0:0

}

.assembly extern System.Windows.Forms

{

.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )

.ver 2:0:0:0

}

Here, each .assembly extern block is qualified by the .publickeytoken and .ver directives. The

.publickeytoken instruction is present only if the assembly has been configured with a strong name (more details later in this chapter). The .ver token marks (of course) the numerical version identifier.

After cataloging each of the external references, you will find a number of .custom tokens that identify assembly-level attributes. If you examine the AssemblyInfo.cs file created by Visual Studio 2005, you will find these attributes represent basic characteristics about the assembly such as company name, trademark, and so forth (all of which are currently empty). Chapter 14 examines attributes in detail, so don’t sweat the details at this point. Do be aware, however, that the attributes defined in AssemblyInfo.cs update the manifest with various .custom tokens, such as [AssemblyTitle]:

.assembly CarLibrary

{

...

.custom instance void [mscorlib]

358 C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 00 00 00 )

.hash algorithm 0x00008004

.ver 1:0:454:30104

}

.module CarLibrary.dll

Finally, you can also see that the .assembly token is used to mark the friendly name of your assembly (CarLibrary), while the .module token specifies the name of the module itself (CarLibrary.dll). The

.ver token defines the version number assigned to this assembly, as specified by the [AssemblyVersion] attribute within AssemblyInfo.cs. More details on assembly versioning later in this chapter, but note that the * wildcard character within the [AssemblyVersion] attribute informs Visual Studio 2005 to increment the build and revision numbers during compilation.

Exploring the CIL

Recall that an assembly does not contain platform-specific instructions; rather, it contains platformagnostic CIL. When the .NET runtime loads an assembly into memory, the underlying CIL is compiled (using the JIT compiler) into instructions that can be understood by the target platform. If you double-click the TurboBoost() method of the SportsCar class, ildasm.exe will open a new window showing the CIL instructions:

.method public hidebysig virtual instance void TurboBoost() cil managed

{

 

 

// Code size

17 (0x11)

.maxstack

2

 

IL_0000:

ldstr

"Ramming speed!"

IL_0005:

ldstr

"Faster is better..."

IL_000a:

call

valuetype [System.Windows.Forms]

System.Windows.Forms.DialogResult [System.Windows.Forms] System.Windows.Forms.MessageBox::Show(string, string)

IL_000f: pop IL_0010: ret

} // end of method SportsCar::TurboBoost

Notice that the .method tag is used to identify a method defined by the SportsCar type. Member variables defined by a type are marked with the .field tag. Recall that the Car class defined a set of protected data, such as currSpeed:

.field family int16 currSpeed

Properties are marked with the .property tag. Here is the CIL describing the public CurrSpeed property (note that the read/write nature of a property is marked by .get and .set tags):

.property instance int16 CurrSpeed()

{

.get instance int16 CarLibrary.Car::get_CurrSpeed()

.set instance void CarLibrary.Car::set_CurrSpeed(int16) } // end of property Car::CurrSpeed

Exploring the Type Metadata

Finally, if you now press Ctrl+M, ildasm.exe displays the metadata for each type within the CarLibrary.dll assembly (see Figure 11-8).

C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

359

Figure 11-8. Type metadata for the types within CarLibrary.dll

Now that you have looked inside the CarLibrary.dll assembly, you can build some client applications.

Source Code The CarLibrary project is located under the Chapter 11 subdirectory.

Building a C# Client Application

Because each of the CarLibrary types has been declared using the public keyword, other assemblies are able to make use of them. Recall that you may also define types using the C# internal keyword (in fact, this is the default C# access mode if you do not specifically define a type as public). Internal types can be used only by the assembly in which they are defined. External clients can neither see nor create internal types.

Note .NET 2.0 now provides a way to specify “friend assemblies” that allow internal types to be consumed by specific assemblies. Look up the InternalsVisibleToAttribute class in the .NET Framework 2.0 SDK documentation for details.

To consume these types, create a new C# console application project (CSharpCarClient). Once you have done so, set a reference to CarLibrary.dll using the Browse tab of the Add Reference dialog box (if you compiled CarLibrary.dll using Visual Studio 2005, your assembly is located under the \Bin\Debug subdirectory of the CarLibrary project folder). Once you click the OK button, Visual Studio 2005 responds by placing a copy of CarLibrary.dll into the \Bin\Debug folder of the CSharpCarClient project folder (see Figure 11-9).

360 C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

Figure 11-9. Visual Studio 2005 copies private assemblies to the client’s directory.

At this point you can build your client application to make use of the external types. Update your initial C# file as so:

using System;

// Don't forget to 'use' the CarLibrary namespace! using CarLibrary;

namespace CSharpCarClient

{

public class CarClient

{

static void Main(string[] args)

{

// Make a sports car.

SportsCar viper = new SportsCar("Viper", 240, 40); viper.TurboBoost();

// Make a minivan.

MiniVan mv = new MiniVan(); mv.TurboBoost(); Console.ReadLine();

}

}

}

This code looks just like the other applications developed thus far. The only point of interest is that the C# client application is now making use of types defined within a separate custom assembly. Go ahead and run your program. As you would expect, the execution of this program results in the display of various message boxes.

Source Code The CSharpCarClient project is located under the Chapter 11 subdirectory.

Building a Visual Basic .NET Client Application

To illustrate the language-agnostic attitude of the .NET platform, let’s create another console application (VbNetCarClient), this time using Visual Basic .NET (see Figure 11-10). Once you have created the project, set a reference to CarLibrary.dll using the Add Reference dialog box.

C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

361

Figure 11-10. Creating a Visual Basic .NET console application

Like C#, Visual Basic .NET requires you to list each namespace used within the current file. However, Visual Basic .NET offers the Imports keyword rather than the C# using keyword. Given this, add the following Imports statement within the Module1.vb code file:

Imports CarLibrary

Module Module1

Sub Main()

End Sub

End Module

Notice that the Main() method is defined within a Visual Basic .NET Module type (which has nothing to do with a *.netmodule file for a multifile assembly). Modules are simply a Visual Basic

.NET shorthand notation for defining a sealed class that can contain only static methods. To drive this point home, here would be the same construct in C#:

//A VB .NET 'Module' is simply a sealed class

//containing static methods.

public sealed class Module1

{

public static void Main()

{

}

}

In any case, to exercise the MiniVan and SportsCar types using the syntax of Visual Basic .NET, update your Main() method as so:

362 C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

Sub Main()

Console.WriteLine("***** Fun with Visual Basic .NET *****")

Dim myMiniVan As New MiniVan() myMiniVan.TurboBoost()

Dim mySportsCar As New SportsCar() mySportsCar.TurboBoost() Console.ReadLine()

End Sub

When you compile and run your application, you will once again find a series of message boxes displayed.

Cross-Language Inheritance in Action

A very enticing aspect of .NET development is the notion of cross-language inheritance. To illustrate, let’s create a new Visual Basic .NET class that derives from SportsCar (which was authored using C#). First, add a new class file to your current Visual Basic .NET application (by selecting Project Add Class) named PerformanceCar.vb. Update the initial class definition by deriving from the SportsCar type using the Inherits keyword. Furthermore, override the abstract TurboBoost() method using the

Overrides keyword:

Imports CarLibrary

' This VB type is deriving from the C# SportsCar.

Public Class PerformanceCar Inherits SportsCar

Public Overrides Sub TurboBoost()

Console.WriteLine("Zero to 60 in a cool 4.8 seconds...") End Sub

End Class

To test this new class type, update the Module’s Main() method as so:

Sub Main()

...

Dim dreamCar As New PerformanceCar()

' Inherited property. dreamCar.PetName = "Hank" dreamCar.TurboBoost() Console.ReadLine()

End Sub

Notice that the dreamCar object is able to invoke any public member (such as the PetName property) found up the chain of inheritance, regardless of the fact that the base class has been defined in a completely different language and is defined in a completely different code library.

Source Code The VbNetCarClient project is located under the Chapter 11 subdirectory.

Building and Consuming a Multifile Assembly

Now that you have constructed and consumed a single-file assembly, let’s examine the process of building a multifile assembly. Recall that a multifile assembly is simply a collection of related modules

C H A P T E R 1 1 I N T R O D U C I N G . N E T A S S E M B L I E S

363

that is deployed and versioned as a single unit. At the time of this writing, Visual Studio 2005 does not support a C# multifile assembly project template. Therefore, you will need to make use of the command-line compiler (csc.exe) if you wish to build such as beast.

To illustrate the process, you will build a multifile assembly named AirVehicles. The primary module (airvehicles.dll) will contain a single class type named Helicopter. The related manifest (also contained in airvehicles.dll) catalogues an additional *.netmodule file named ufo.netmodule, which contains another class type named (of course) Ufo. Although both class types are physically contained in separate binaries, you will group them into a single namespace named AirVehicles. Finally, both classes are created using C# (although you could certainly mix and match languages if you desire).

To begin, open a simple text editor (such as Notepad) and create the following Ufo class definition saved to a file named ufo.cs:

using System;

namespace AirVehicles

{

public class Ufo

{

public void AbductHuman()

{

Console.WriteLine("Resistance is futile");

}

}

}

To compile this class into a .NET module, navigate to the folder containing ufo.cs and issue the following command to the C# compiler (the module option of the /target flag instructs csc.exe to produce a *.netmodule as opposed to a *.dll or *.exe file):

csc.exe /t:module ufo.cs

If you now look in the folder that contains the ufo.cs file, you should see a new file named ufo.netmodule (take a peek). Next, create a new file named helicopter.cs that contains the following class definition:

using System;

namespace AirVehicles

{

public class Helicopter

{

public void TakeOff()

{

Console.WriteLine("Helicopter taking off!");

}

}

}

Given that airvehicles.dll is the intended name of the primary module of this multifile assembly, you will need to compile helicopter.cs using the /t:library and /out: options. To enlist the ufo.netmodule binary into the assembly manifest, you must also specify the /addmodule flag. The following command does the trick:

csc /t:library /addmodule:ufo.netmodule /out:airvehicles.dll helicopter.cs

At this point, your directory should contain the primary airvehicles.dll module as well as the secondary ufo.netmodule binaries.