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

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

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

P A R T 3

■ ■ ■

Programming with .NET Assemblies

C H A P T E R 1 1

■ ■ ■

Introducing .NET Assemblies

Each of the applications developed in this book’s first ten chapters were along the lines of traditional “stand-alone” applications, given that all of your custom programming logic was contained within a single executable file (*.exe). However, one major aspect of the .NET platform is the notion of binary reuse, where applications make use of the types contained within various external assemblies (aka code libraries). The point of this chapter is to examine the core details of creating, deploying, and configuring .NET assemblies.

In this chapter, you’ll first learn the distinction between single-file and multifile assemblies, as well as “private” and “shared” assemblies. Next, you’ll examine exactly how the .NET runtime resolves the location of an assembly and come to understand the role of the Global Assembly Cache (GAC), application configuration files (*.config files), publisher policy assemblies, and the

System.Configuration namespace.

The Role of .NET Assemblies

.NET applications are constructed by piecing together any number of assemblies. Simply put, an assembly is a versioned, self-describing binary file hosted by the CLR. Now, despite the fact that .NET assemblies have exactly the same file extensions (*.exe or *.dll) as previous Win32 binaries (including legacy COM servers), they have very little in common under the hood. Thus, to set the stage for the information to come, let’s ponder some of the benefits provided by the assembly format.

Assemblies Promote Code Reuse

As you have been building your console applications over the previous chapters, it may have seemed that all of the applications’ functionality was contained within the executable assembly you were constructing. In reality, your applications were leveraging numerous types contained within the always accessible .NET code library, mscorlib.dll (recall that the C# compiler references mscorlib.dll automatically), as well as System.Windows.Forms.dll.

As you may know, a code library (also termed a class library) is a *.dll that contains types intended to be used by external applications. When you are creating executable assemblies, you will no doubt be leveraging numerous system-supplied and custom code libraries as you create the application at hand. Do be aware, however, that a code library need not take a *.dll file extension. It is perfectly possible for an executable assembly to make use of types defined within an external executable file. In this light, a referenced *.exe can also be considered a “code library.”

347

Note

348 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

Before the release of Visual Studio 2005, the only way to reference an executable code library was using the /reference flag of the C# compiler. However, the Add Reference dialog box of Visual Studio 2005 now allows you to reference *.exe assemblies.

Regardless of how a code library is packaged, the .NET platform allows you to reuse types in

a language-independent manner. For example, you could create a code library in C# and reuse that library in any other .NET programming language. It is possible to not only allocate types across languages, but derive from them as well. A base class defined in C# could be extended by a class authored in Visual Basic .NET. Interfaces defined in Pascal .NET can be implemented by structures defined in C#, and so forth. The point is that when you begin to break apart a single monolithic executable into numerous .NET assemblies, you achieve a language-neutral form of code reuse.

Assemblies Establish a Type Boundary

In Chapter 3, you learned about the formalities behind .NET namespaces. Recall that a type’s fully qualified name is composed by prefixing the type’s namespace (e.g., System) to its name (e.g., Console). Strictly speaking however, the assembly in which a type resides further establishes a type’s identity. For example, if you have two uniquely named assemblies (say, MyCars.dll and YourCars.dll) that both define a namespace (CarLibrary) containing a class named SportsCar, they are considered unique types in the .NET universe.

Assemblies Are Versionable Units

.NET assemblies are assigned a four-part numerical version number of the form <major>.<minor>. <build>.<revision> (if you do not explicitly provide a version number using the [AssemblyVersion] attribute, the assembly is automatically assigned a version of 0.0.0.0). This number, in conjunction with an optional public key value, allows multiple versions of the same assembly to coexist in harmony on a single machine. Formally speaking, assemblies that provide public key information are termed strongly named. As you will see in this chapter, using a strong name, the CLR is able to ensure that the correct version of an assembly is loaded on behalf of the calling client.

Assemblies Are Self-Describing

Assemblies are regarded as self-describing in part because they record every external assembly it must have access to in order to function correctly. Thus, if your assembly requires System.Windows. Forms.dll and System.Drawing.dll, they will be documented in the assembly’s manifest. Recall from Chapter 1 that a manifest is a blob of metadata that describes the assembly itself (name, version, external assemblies, etc.).

In addition to manifest data, an assembly contains metadata that describes the composition (member names, implemented interfaces, base classes, constructors and so forth) of every contained type. Given that an assembly is documented in such vivid detail, the CLR does not consult the Win32 system registry to resolve its location (quite the radical departure from Microsoft’s legacy COM programming model). As you will discover during this chapter, the CLR makes use of an entirely new scheme to resolve the location of external code libraries.

Assemblies Are Configurable

Assemblies can be deployed as “private” or “shared.” Private assemblies reside in the same directory (or possibly a subdirectory) as the client application making use of them. Shared assemblies, on the

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

349

other hand, are libraries intended to be consumed by numerous applications on a single machine and are deployed to a specific directory termed the Global Assembly Cache (GAC).

Regardless of how you deploy your assemblies, you are free to author XML-based configuration files. Using these configuration files, the CLR can be instructed to “probe” for assemblies under a specific location, load a specific version of a referenced assembly for a particular client, or consult an arbitrary directory on your local machine, your network location, or a web-based URL. You’ll learn a good deal more about XML configuration files throughout this chapter.

Understanding the Format of a .NET Assembly

Now that you’ve learned about several benefits provided by the .NET assembly, let’s shift gears and get a better idea of how an assembly is composed under the hood. Structurally speaking, a .NET assembly (*.dll or *.exe) consists of the following elements:

A Win32 file header

A CLR file header

CIL code

Type metadata

An assembly manifest

Optional embedded resources

While the first two elements (the Win32 and CLR headers) are blocks of data that you can typically ignore, they do deserve some brief consideration. This being said, an overview of each element follows.

The Win32 File Header

The Win32 file header establishes the fact that the assembly can be loaded and manipulated by the Windows family of operating systems. This header data also identifies the kind of application (consolebased, GUI-based, or *.dll code library) to be hosted by the Windows operating system. If you open a .NET assembly using the dumpbin.exe utility (via a .NET Framework 2.0 SDK command prompt) and specify the /headers flag, you can view an assembly’s Win32 header information. Figure 11-1 shows (partial) Win32 header information for the CarLibrary.dll assembly you will build a bit later in this chapter.

350 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-1. An assembly’s Win32 file header information

The CLR File Header

The CLR header is a block of data that all .NET files must support (and do support, courtesy of the C# compiler) in order to be hosted by the CLR. In a nutshell, this header defines numerous flags that enable the runtime to understand the layout of the managed file. For example, flags exist that identify the location of the metadata and resources within the file, the version of the runtime the assembly was built against, the value of the (optional) public key, and so forth. If you supply the /clrheader flag to dumpbin.exe, you are presented with the internal CLR header information for a given .NET assembly, as shown in Figure 11-2.

Figure 11-2. An assembly’s CLR file header information

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

351

CLR header data is represented by an unmanaged C-style structure (IMAGE_COR20_HEADER) defined in the C-based header file, corhdr.h. For those who are interested, here is the layout of the structure in question:

// CLR 2.0 header structure. typedef struct IMAGE_COR20_HEADER

{

//Header versioning

ULONG cb;

USHORT MajorRuntimeVersion; USHORT MinorRuntimeVersion;

//Symbol table and startup information

IMAGE_DATA_DIRECTORY MetaData; ULONG Flags;

ULONG EntryPointToken;

// Binding information

IMAGE_DATA_DIRECTORY Resources; IMAGE_DATA_DIRECTORY StrongNameSignature;

// Regular fixup and binding information

IMAGE_DATA_DIRECTORY CodeManagerTable; IMAGE_DATA_DIRECTORY VTableFixups; IMAGE_DATA_DIRECTORY ExportAddressTableJumps;

// Precompiled image info (internal use only - set to zero)

IMAGE_DATA_DIRECTORY ManagedNativeHeader; } IMAGE_COR20_HEADER;

Again, as a .NET developer you will not need to concern yourself with the gory details of Win32 or CLR header information (unless perhaps you are building a new managed compiler!). Just understand that every .NET assembly contains this data, which is used behind the scenes by the .NET runtime and Win32 operating system.

CIL Code, Type Metadata, and the Assembly Manifest

At its core, an assembly contains CIL code, which as you recall is a platformand CPU-agnostic intermediate language. At runtime, the internal CIL is compiled on the fly (using a just-in-time [JIT] compiler) to platformand CPU-specific instructions. Given this architecture, .NET assemblies can indeed execute on a variety of architectures, devices, and operating systems. Although you can live a happy and productive life without understanding the details of the CIL programming language, Chapter 15 offers an introduction to the syntax and semantics of CIL.

An assembly also contains metadata that completely describes the format of the contained types as well as the format of external types referenced by this assembly. The .NET runtime uses this metadata to resolve the location of types (and their members) within the binary, lay out types in memory, and facilitate remote method invocations. You’ll check out the details of the .NET metadata format in Chapter 12 during our examination of reflection services.

An assembly must also contain an associated manifest (also referred to as assembly metadata). The manifest documents each module within the assembly, establishes the version of the assembly, and also documents any external assemblies referenced by the current assembly (unlike legacy COM type libraries, which did not provide a way to document external dependencies). As you will see over the course of this chapter, the CLR makes extensive use of an assembly’s manifest during the process of locating external assembly references.

352 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

Note Needless to say by this point in the book, when you wish to view an assembly’s CIL code, type metadata, or manifest, ildasm.exe is the tool of choice. I will assume you will make extensive use of ildasm.exe as you work through the code examples in this chapter.

Optional Assembly Resources

Finally, a .NET assembly may contain any number of embedded resources such as application icons, image files, sound clips, or string tables. In fact, the .NET platform supports satellite assemblies that contain nothing but localized resources. This can be useful if you wish to partition your resources based on a specific culture (English, German, etc.) for the purposes of building international software. The topic of building satellite assemblies is outside the scope of this text; however, you will learn how to embed application resources into an assembly during our examination of GDI+ in Chapter 20.

Single-File and Multifile Assemblies

Technically speaking, an assembly can be composed of multiple modules. A module is really nothing more than a generic term for a valid .NET binary file. In most situations, an assembly is in fact composed of a single module. In this case, there is a one-to-one correspondence between the (logical) assembly and the underlying (physical) binary (hence the term single-file assembly).

Single-file assemblies contain all of the necessary elements (header information, CIL code, type metadata, manifest, and required resources) in a single *.exe or *.dll package. Figure 11-3 illustrates the composition of a single-file assembly.

Figure 11-3. A single-file assembly

A multifile assembly, on the other hand, is a set of .NET *.dlls that are deployed and versioned as a single logic unit. Formally speaking, one of these *.dlls is termed the primary module and contains the assembly-level manifest (as well as any necessary CIL code, metadata, header information, and optional resources). The manifest of the primary module records each of the related *.dll files it is dependent upon.

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

353

As a naming convention, the secondary modules in a multifile assembly take a *.netmodule file extension; however, this is not a requirement of the CLR. Secondary *.netmodules also contain CIL code and type metadata, as well as a module-level manifest, which simply records the externally required assemblies of that specific module.

The major benefit of constructing multifile assemblies is that they provide a very efficient way to download content. For example, assume you have a machine that is referencing a remote multifile assembly composed of three modules, where the primary module is installed on the client. If the client requires a type within a secondary remote *.netmodule, the CLR will download the binary to the local machine on demand to a specific location termed the download cache. If each *.netmodule is 1MB, I’m sure you can see the benefit.

Another benefit of multifile assemblies is that they enable modules to be authored using multiple

.NET programming languages (which is very helpful in larger corporations, where individual departments tend to favor a specific .NET language). Once each of the individual modules has been compiled, the modules can be logically “connected” into a logical assembly using tools such as the assembly linker (al.exe).

In any case, do understand that the modules that compose a multifile assembly are not literally linked together into a single (larger) file. Rather, multifile assemblies are only logically related by information contained in the primary module’s manifest. Figure 11-4 illustrates a multifile assembly composed of three modules, each authored using a unique .NET programming language.

Figure 11-4. The primary module records secondary modules in the assembly manifest