Assembly Identification

An assembly is uniquely identified by four parts—its name, version, culture, and public key. The assembly resolver, a part of the common language run-time that is responsible for locating assemblies, uses this four-part information to locate the correct assembly.

Name

The Name part of the assembly name typically corresponds to the underlying filename (without the extension) of the assembly.

Strictly speaking, the Name of the assembly need not match the underlying filename. However, keeping the two names in sync makes the job of the assembly resolver (and humans) easier.

Version

Each assembly has a four-part version number of the form Major.Minor.Build.Revision. This version number is set at build time using an assembly-level attribute (attributes provide extra information on parts of your code) called AssemblyVersionAttribute (namespace System.Reflection), as illustrated here:

// File ConsoleGreeting.cs
[assembly: AssemblyVersionAttribute("1.2.3.4")]

If the version number is not explicitly set on an assembly, the default value is 0.0.0.0.

When specifying the version number, only the Major field is mandatory. Any other missing fields are assumed to be zero. For example, specifying a version string of 1.2 results in an actual version value of 1.2.0.0.

Using Visual Studio .NET IDE

If you create your .NET project using the Visual Studio .NET Integrated Development Environment (IDE), the IDE automatically generates AssemblyVersionAttribute, and other related attributes, stores them in a file named AssemblyInfo.cs, and adds the file to the project.


It is also possible to let the compiler generate the build and the revision number. This is done by replacing the build and the revision field by a single asterick, as in 1.2.*. In this case, the compiler generates the build number as the number of days that have elapsed since January 1, 2000 and the revision number as half the number of seconds since midnight.

You can also use * just for the revision number, but not for the build number. For example, 1.2.3.* is legal but 1.2.*.3 is illegal.

Culture

A culture, in simple terms, identifies a specific language and optionally a sublanguage (e.g., Australian English). Associated with the language (and the sublanguage) also are some culture-specific operations such as currency, number, and date formatting.

Each culture is identified by a name. The naming scheme is based on Request for Comments (RFC) 1766 [Alv-95]. For example, U.S. English is “en-US” and Spanish from Spain is “es-ES.”

Culture settings are typically used to build resource-only assemblies, commonly known as satellite assemblies. The main idea behind building satellite assemblies is that an application can load a resource from the appropriate satellite assembly, based on the current culture setting of the application.

The culture setting is assigned to a satellite assembly at the time of building the assembly.

We will look into building satellite assemblies in a later section. For now, it is important to note that the assemblies containing the code are marked as culture-neutral—such an assembly can contain resources that can run under any culture settings.

Public Key

The .NET Framework provides a mechanism to guarantee that an assembly has not been tampered with, perhaps by a malicious hacker, once the assembly has been created. This is done by using standard public-key cryptographic techniques—the assembly is signed with a cryptographic public–private key pair. Such an assembly is called strong-named assembly. Assemblies that are not strong-named are referred to as simple assemblies.

Strong-named assemblies are also useful to ensure correct binding between an application and its referenced assemblies. For each strong-named assembly that the application references (or loads dynamically), the common language runtime tries to bind the assembly that has the exact name, version, culture, and public key. The importance of this will become evident when we discuss the “DLL hell” problem later in the chapter.

A Restriction on Strong-Named Assemblies

When you use a strong-named assembly, you get certain benefits such as versioning and integrity check. If a strong-named assembly in turn references a simple-named assembly, these benefits are lost. Therefore, the framework prohibits a strong-named assembly from referencing simple-named assemblies.


To build a strong-named assembly, you must first obtain a public–private key pair. The framework provides a utility called the Strong Name tool (sn.exe) that can be used to generate the key pair. The following command line, for example, generates a key pair and stores it in MyKey.snk:

sn.exe –k MyKey.snk

Switch -k is used to generate a key pair and store it in the specified file (.snk is the typical extension for such files). The public key is made of a 128-byte blob with 32 bytes of header information. The private key is made of a 436-byte blob. This brings the size of the generated public–private key pair file to 596 bytes.

An assembly can be signed with the strong-named key file by using an attribute, AssemblyKeyFileAttribute (namespace System.Reflection), in any one of the source files. The following source line, for example, specifies that the assembly be signed using MyKey.snk:

[assembly: AssemblyKeyFileAttribute("MyKey.snk")]

When this attribute is specified, the compiler takes care of signing the resulting assembly with the specified filename.

Project SharedAssembly on the companion Web site demonstrates building shared assemblies.

Note that there are also some other ways to sign an assembly. For example, the .NET Framework provides a tool called the Assembly Linker (al.exe) that can be used to sign the assembly. The tool uses a command-line switch -keyfile for this purpose.

Building from VS .NET and Command Line

If a complete path to the key file is not specified to AssemblyKeyFile attribute, the build process expects the file to be in a directory relative to the directory from which the build was initiated. Almost all the projects on the companion Web site that create strong-named assemblies have the key file in the output directory (Bin). This directory also contains the makefile for command line builds. In order to be able to build the assemblies from VS .NET as well as from the command line makefile, the source code defines the key file attribute as follows:

#if CMDLINE
[assembly: AssemblyKeyFile("MyKey.snk")]
#else
[assembly: AssemblyKeyFile(@".BinMyKey.snk")]
#endif

The makefile defines the symbol CMDLINE, as shown in the following example:

csc.exe -define:CMDLINE ...

Using a conditional directive in the source code for the key file attribute to achieve builds from VS .NET as well as from the command line is a common programming technique under .NET. You will see this technique being used in many .NET SDK samples.


Public keys are represented by a large number of bytes (a 128-byte blob with 32 bytes of header information). To conserve storage space, the framework hashes the public key and takes the last 8 bytes of the hashed value. This reduced public key value, also known as the public key token, has been determined to be statistically unique.

Obtaining the Public Key Token

To get the public key token from a strong-named assembly, run sn.exe in a command window with the -T command-line switch, as shown here:

sn.exe –T ConsoleGreeting.dll

Using sn.exe -T is also a good way to check if an assembly is strong-named.

Note that the command-line switch specified is uppercase T. Do not make the mistake of using –t (lowercase). It results in displaying a bogus public key token. This switch is meant to be specified on a file that contains only the public key (as opposed to the public–private key pair).


The combination of the name, version, and culture, along with the public key (or its token) creates a unique identity for an assembly. Assemblies with the same strong name are expected to be identical. It is impossible for a hacker to create a new assembly with exactly the same name and the same public key as your assembly. A strong name also ensures that no one else can produce a subsequent version of your assembly (as long as the cryptographic key file is not leaked out).

Note that an assembly that is not cryptographically signed has a public key token value of null, as we saw in the output of the last example that we ran.

The common language runtime defines a standard format to represent the four parts as a string. This string representation is called the display name of the assembly. Its format is shown here:

Name <,Version=value> <,Culture=value> <,PublicKeyToken=value>

Although an assembly can be loaded by using its filename, as we saw earlier, a more common technique is to load the assembly by means of its display name. This is done using a static method Assembly.Load, as illustrated in the following code:

// Project LoadAssembly

     public static void Main(String[] args) {
       ...
       String name =
       "ConsoleGreeting, Version=1.2.3.4, Culture=neutral,
            PublicKeyToken=null";
       Assembly b = Assembly.Load(name);
       ...
     }

Method Assembly.Load causes the resolver to look into the application directory (and some other subdirectories of the application). Only the Name property of the assembly name is mandatory. All other parts are optional. Here are some examples of identifying an assembly:

ConsoleGreeting,Version=1.2.3.4,Culture="",
     PublicKeyToken=4028b28a1c16b46e
ConsoleGreeting,Version=1.2.3.4,Culture=""
ConsoleGreeting,Version=1.2.3.4
ConsoleGreeting
ConsoleGreeting, PublicKeyToken=4028b28a1c16b46e

Note that there is a difference between Culture=neutral and Culture="" or omitting the Culture keyword altogether. In the first case, the assembly resolver looks for a culture-neutral assembly. In the second case, the resolver matches any Culture settings.

A similar difference exists between PublicKeyToken=null and no PublicKeyToken keyword. The first case indicates that an assembly to be loaded does not have any public key. In the second case, the resolver matches any PublicKeyToken setting.

Here is an example of loading an assembly by using just the Name field:

// Project LoadAssembly

     public static void Main(String[] args) {
       ...
       String name = "ConsoleGreeting";
       Assembly c = Assembly.Load(name);
       ...
     }

Note that the Name of the assembly does not contain any extension. The assembly resolver automatically appends an extension to the name. In the preceding case, the resolver first tries to load ConsoleGreeting.dll, failing which it tries to load ConsoleGreeting.exe.

If the resolver cannot locate an assembly, it throws a standard exception of type System.IO.FileNotFoundException.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.136.165