
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
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){ }


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).


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:


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.