
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
492 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
Figure 15-9. Type metadata for the types within CarLibrary.dll
As explained in the next chapter, an assembly’s metadata is a very important trait of the .NET platform, and serves as the backbone for numerous technologies (object serialization, late binding, extendable applications, etc.). In any case, now that you have looked inside the CarLibrary.dll assembly, you can build some client applications that make use of your types.
■Source Code The CarLibrary project is located under the Chapter 15 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). Internal types can be used only by the assembly in which they are defined. External clients can neither see nor create types marked with the internal keyword.
■Note .NET does provides a way to specify “friend assemblies” that allow internal types to be consumed by a set of specified assemblies. Look up the InternalsVisibleToAttribute class in the .NET Framework 3.5 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, your assembly is located under the \bin\Debug subdirectory of the CarLibrary project folder). At this point you can build your client application to make use of the external types. Update your initial C# file as follows:


494 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
As explained later in this chapter, CarLibrary.dll has been deployed as a “private” assembly. Therefore the CLR loads the local copy of the .NET binary on behalf of the current client (CSharpCarClient.exe).
■Source Code The CSharpCarClient project is located under the Chapter 15 subdirectory.
Building a Visual Basic Client Application
To illustrate the language-agnostic attitude of the .NET platform, let’s create another Console Application (VbNetCarClient), this time using Visual Basic (see Figure 15-11). Once you have created the project, set a reference to CarLibrary.dll using the Add Reference dialog box, which can be activated by the Project Add Reference menu option.
Figure 15-11. Creating a Visual Basic Console Application
Like C#, Visual Basic requires you to list each namespace used within the current file. However, Visual Basic 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

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
495 |
Notice that the Main() method is defined within a Visual Basic module type (which has nothing to do with a *.netmodule file for a multifile assembly). In a nutshell, modules are a Visual Basic notation for defining a sealed class that can contain only static methods. In any case, to exercise the MiniVan and SportsCar types using the syntax of Visual Basic, update your Main() method as follows:
Sub Main()
Console.WriteLine("***** VB CarLibrary Client App *****")
' Local variables are declared using the Dim keyword.
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. Furthermore, this new client application has its own local copy of CarLibrary.dll located under the bin\Debug folder.
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 class that derives from SportsCar (which was authored using C#). First, add a new class file to your current Visual Basic application (by selecting the Project Add Class menu option) 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 follows:
Sub Main()
...
Dim dreamCar As New PerformanceCar()
' Use 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 assembly.

496 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
■Source Code The VbNetCarClient project is located under the Chapter 15 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 that is deployed and versioned as a single logical unit. At the time of this writing, the Visual Studio IDE 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 a 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) catalogs 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 an *.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!");

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
497 |
}
}
}
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.
Exploring the ufo.netmodule File
Now, using ildasm.exe, open ufo.netmodule. As you can see, *.netmodules contain a module-level manifest; however, its sole purpose is to list each external assembly referenced by the code base. Given that the Ufo class did little more than make a call to Console.WriteLine(), you find the following:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.module ufo.netmodule
Exploring the airvehicles.dll File
Next, using ildasm.exe, open the primary airvehicles.dll module and investigate the assemblylevel manifest. Notice that the .file token documents the associated modules in the multifile assembly (ufo.netmodule in this case). The .class extern tokens are used to document the names of the external types referenced for use from the secondary module (Ufo):
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly airvehicles
{
...
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.file ufo.netmodule
...
.class extern public AirVehicles.Ufo
{
.file ufo.netmodule
.class 0x02000002
}
.module airvehicles.dll


CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
499 |
Understanding Private Assemblies
Technically speaking, the assemblies you’ve created thus far in this chapter have been deployed as private assemblies. Private assemblies are required to be located within the same directory as the client application (termed the application directory) or a subdirectory thereof. Recall that when you set a reference to CarLibrary.dll while building the CSharpCarClient.exe and VbNetCarClient.exe applications, Visual Studio 2008 responded by placing a copy of CarLibrary.dll within the client’s application directory (at least, after the first compilation).
When a client program uses the types defined within this external assembly, the CLR simply loads the local copy of CarLibrary.dll. Because the .NET runtime does not consult the system registry when searching for referenced assemblies, you can relocate the CSharpCarClient.exe (or VbNetCarClient.exe) and CarLibrary.dll assemblies to a new location on your machine and run the application (this is often termed Xcopy deployment).
Uninstalling (or replicating) an application that makes exclusive use of private assemblies is a no-brainer: simply delete (or copy) the application folder. Unlike with COM applications, you do not need to worry about dozens of orphaned registry settings. More important, you do not need to worry that the removal of private assemblies will break any other applications on the machine.
The Identity of a Private Assembly
The full identity of a private assembly consists of the friendly name and numerical version, both of which are recorded in the assembly manifest. The friendly name simply is the name of the module that contains the assembly’s manifest minus the file extension. For example, if you examine the manifest of the CarLibrary.dll assembly, you find the following:
.assembly CarLibrary
{
...
.ver 1:0:0:0
}
Given the isolated nature of a private assembly, it should make sense that the CLR does not bother to make use of the version number when resolving its location. The assumption is that private assemblies do not need to have any elaborate version checking, as the client application is the only entity that “knows” of its existence. Given this, it is (very) possible for a single machine to have multiple copies of the same private assembly in various application directories.
Understanding the Probing Process
The .NET runtime resolves the location of a private assembly using a technique termed probing, which is much less invasive than it sounds. Probing is the process of mapping an external assembly request to the location of the requested binary file. Strictly speaking, a request to load an assembly may be either implicit or explicit. An implicit load request occurs when the CLR consults the manifest in order to resolve the location of an assembly defined using the .assembly extern tokens:
// An implicit load request.
.assembly extern CarLibrary { ... }
An explicit load request occurs programmatically using the Load() or LoadFrom() method of the System.Reflection.Assembly class type, typically for the purposes of late binding and dynamic invocation of type members. You’ll examine these topics further in Chapter 16, but for now you can see an example of an explicit load request in the following code:

500 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
// An explicit load request based on a friendly name.
Assembly asm = Assembly.Load("CarLibrary");
In either case, the CLR extracts the friendly name of the assembly and begins probing the client’s application directory for a file named CarLibrary.dll. If this file cannot be located, an attempt is made to locate an executable assembly based on the same friendly name (CarLibrary. exe). If neither of these files can be located in the application directory, the runtime gives up and throws a FileNotFoundException exception at runtime.
■Note Technically speaking, if a copy of the requested assembly cannot be found within the client’s application directory, the CLR will also attempt to locate a client subdirectory with the exact same name as the assembly’s friendly name (e.g., C:\MyClient\CarLibrary). If the requested assembly resides within this subdirectory, the CLR will load the assembly into memory.
Configuring Private Assemblies
While it is possible to deploy a .NET application by simply copying all required assemblies to a single folder on the user’s hard drive, you will most likely wish to define a number of subdirectories to group related content. For example, assume you have an application directory named C:\MyApp that contains CSharpCarClient.exe. Under this folder might be a subfolder named MyLibraries that contains CarLibrary.dll.
Regardless of the intended relationship between these two directories, the CLR will not probe the MyLibraries subdirectory unless you supply a configuration file. Configuration files contain various XML elements that allow you to influence the probing process. Configuration files must have the same name as the launching application and take a *.config file extension, and they must be deployed in the client’s application directory. Thus, if you wish to create a configuration file for
CSharpCarClient.exe, it must be named CSharpCarClient.exe.config and located (for this example) under the C:\MyApp directory.
To illustrate the process, create a new directory on your C drive named MyApp using Windows Explorer. Next, copy CSharpCarClient.exe and CarLibrary.dll to this new folder, and run the program by double-clicking the executable. Your program should run successfully at this point (remember, the assemblies are not registered!). Next, create a new subdirectory under C:\MyApp named MyLibraries (see Figure 15-12), and move CarLibrary.dll to this location.
Try to run your client program again. Because the CLR could not locate an assembly named “CarLibrary” directly within the application directory, you are presented with a rather nasty unhandled FileNotFoundException exception.
To instruct the CLR to probe under the MyLibraries subdirectory, create a new configuration file named CSharpCarClient.exe.config and save it in the same folder containing the CSharpCarClient.exe application, which in this example would be C:\MyApp. Open this file
and enter the following content exactly as shown (be aware that XML is case sensitive!):
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="MyLibraries"/>
</assemblyBinding>
</runtime>
</configuration>

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
501 |
Figure 15-12. CarLibrary.dll now resides under the MyLibraries subdirectory.
.NET *.config files always open with a root element named <configuration>. The nested <runtime> element may specify an <assemblyBinding> element, which nests a further element named <probing>. The privatePath attribute is the key point in this example, as it is used to specify the subdirectories relative to the application directory where the CLR should probe.
Do note that the <probing> element does not specify which assembly is located under a given subdirectory. In other words, you cannot say, “CarLibrary is located under the MyLibraries subdirectory, but MathUtils is located under the Bin subdirectory.” The <probing> element simply instructs the CLR to investigate all specified subdirectories for the requested assembly until the first match is encountered.
■Note Be very aware that the privatePath attribute cannot be used to specify an absolute (C:\SomeFolder\ SomeSubFolder) or relative (..\\SomeFolder\\AnotherFolder) path! If you wish to specify a directory outside the client’s application directory, you will need to make use of a completely different XML element named <codeBase> (more details on this element later in the chapter).
Multiple subdirectories can be assigned to the privatePath attribute using a semicolondelimited list. You have no need to do so at this time, but here is an example that informs the CLR to consult the MyLibraries and MyLibraries\Tests client subdirectories:
<probing privatePath="MyLibraries; MyLibraries\Tests"/>
Once you’ve finished creating CSharpCarClient.exe.config, run the client by double-clicking the executable in Windows Explorer. You should find that CSharpCarClient.exe executes without a hitch (if this is not the case, double-check your *.config file for typos).
Next, for testing purposes, change the name of your configuration file (in one way or another) and attempt to run the program once again. The client application should now fail. Remember that *.config files must be prefixed with the same name as the related client application. By way of a final test, open your configuration file for editing and capitalize any of the XML elements. Once the file is saved, your client should fail to run once again (as XML is case sensitive).