Assemblies

Summary

Overview

Assemblies are binaries used to package and distribute executable code. There are similar to Win32 DLLs and EXEs in functionality, but they provide much more functionality than their Win32 DLLs and EXE counterpart. Specifically, assemblies provide the following functions:

Assemblies can be either static or dynamic. Static assemblies are the ones you usually deal with - they are stored and loaded form the disk. Dynamic assemblies on the other hand are usually created using Reflection.Emit and are run directly from memory are not saved to disk before execution.

As assembly consists of one or more physical files called modules. A modules in an executable file containing code and metadata. Each module may be produced using different languages. Also note that only one module in an assembly contains the manifest.

Assembly Benefits

To solve problems associated with DLL Hell and versioning problems, the CLR uses assemblies to do the following:

Because assemblies are self-describing deployment units that have no dependencies on registry entries, they enable zero-impact application installation. They also simplify uninstalling and replicating applications.

Assembly Contents

In general, a static assembly (the regular assemblies generated when compiling your code) consists of four elements:

There are several ways to group these elements in an assembly. In the usual scenario, all these elements are embedded within a single assembly.  Alternatively, these elements can be contained in several files (multi-file assembly). See MSDN for more details on multi-file assemblies.

Assembly Manifest

The assembly manifest is a collection of data that describe how elements in the assembly relate to each other. Assemblies, whether static or dynamic, always contain a manifest. Specifically, an assembly's manifest contains the following information:

Each assembly's manifest performs the following functions:

Assembly Manifest Contents

The following table shows the kind of information you can expect to find in an assembly's manifest. The first four items - assembly name, version number, culture, and strong name - make up the assembly's identity:

Information Description
Assembly Name A text describing assembly's name
Version Major.Minor.Revision.Build number. This number is used to enforce versioning policy.
Culture Information on the culture or language supported by the assembly. This information should only be used to designate an assembly as a satellite assembly containing language or culture-specific information.
Strong Name The public key from the publisher if the assembly has been given a strong name (sn.exe utility)
List of all files in the assembly A hash of each file contained in the assembly and its corresponding file name
Type reference information Information used by the runtime to map a type reference to the file that contains its declaration and implementation.
Information on referenced assemblies A list of other assemblies that this assembly depends on. Note that this information includes the dependent assembly's version and public key (among others)
Informational attributes Generic text-based information such as company name, trademark, and other attributes used 

The following shows the actual information contained in a DLL's manifest:

// Information on referenced assemblies
.assembly extern System
{
    .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
    .ver 1:0:5000:0
}
.assembly extern mscorlib
{
    .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
    .ver 1:0:5000:0
}
.assembly extern System.Drawing
{
    .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
    .ver 1:0:5000:0
}
.assembly extern System.Windows.Forms
{
    .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
    .ver 1:0:5000:0
}
.assembly extern System.Runtime.Serialization.Formatters.Soap
{
    .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
    .ver 1:0:5000:0
}

.assembly Components
{
     // Informational attributes
    .custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyKeyNameAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyDelaySignAttribute::.ctor(bool) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    // --- The following custom attribute is added automatically, do not uncomment -------
    // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
    // bool) = ( 01 00 01 01 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 00 00 00 ) 
    .hash algorithm 0x00008004

     // Version number 
    .ver 1:0:1574:31910
}
.mresource public Components.MyComponent.resources
{
}
.mresource public Components.Interfaces.resources
{
}

// Assembly Name
.module Components.dll
// MVID: {1CF22289-72C4-471D-A6F9-CB15F9FDA96E}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 4096
.corflags 0x00000001
// Image base: 0x03320000

Global Assembly Cache

The Global Assembly Cache (GAC) is a machine-wide cache of code that stores assemblies specifically designated to be shared across multiple applications. The general rule on using the GAC is:

Unless sharing an assembly is explicitly required, keep assembly dependencies private and locate assemblies in the application's directory structure. Install assemblies in the GAC only when you need to share them.

There are several ways to deploy an assembly the GAC:

Assemblies deployed in the GAC must have a strong name. When as assembly is added to the GAC, integrity checks are performed on all files that make up the assembly to ensure that the assembly has not been tampered with.

Assembly Name

All assemblies have a four-part name that uniquely identify the locale and developer of the component:

  1. Simple text-based name that corresponds the file' name (with no extension).
  2. A version in the form of <Major>.<Minor>.<Build>.<Revision> ( see Assembly Versioning for full details).
  3. An optional culture information (CulturInfo) that corresponds to language and region.
  4. An optional Originator / PublicKey that identifies the developer.

For example, the following code (found in AssemblyInfo.cs or .vb):

using System.Reflection

[assembly:AssemblyVersion("1.2.3.4")]
[assembly:AssemblyCulture("en-US")]
[assembly:AssemblyKeyFile("MyKey.snk")]

results in the following full assembly name

MyComponent, Version=1.2.3.4, Culture=en-US, PublicKeyToken=1234567812345678

Strong-Named Assemblies

A strong name consists of three main components:

Strong names can be generated using the sn.exe command-line too. Assemblies having the same strong name are expected to be identical. Strong-named assemblies satisfy the following requirements:

Note that strong-named assemblies can only reference other strong-named assemblies. The following shows the 2 processes for generating strong-named assemblies.

Strong-name with no-delay sign

Strong-name with delay sign

 

As assembly can be signed in two different but complementary ways: using sn.exe or using signcode.exe. Signing an assembly with sn.exe adds a public key encryption to the file containing the assembly's manifest. However, no level of trust is associated with a strong name. Enter signcode.exe. Signcode requires an assembly developer/publisher to prove their identity to a third-party authority and obtain a certificate. signcode.exe embeds this certificate into your assembly. The administrator installing your assembly can then decide whether to trust your assembly or not based on its certificate.

Note that you give an assembly both a strong name and a digital signature, or you can use either alone. A strong name is stored in the file containing the assembly's manifest. Likewise, signcode signs the file containing the assembly's manifest. When you assign a strong name and sign with signcode, the strong name must be given first.

Because strong naming and signcoding guarantee integrity, you can base code access security of these two forms of assembly evidence.

Loading and Resolving Assemblies

Assemblies can be loaded dynamically using either an explicit URL denoting the assembly's location or the four-part full assembly name. 

The following code shows how Assemblies.LoadFrom loads an assembly:

using System;
using System.Reflection;

public void LoadAssembly( string strURL, string strClassName )
{
    // assume strURL is: file://c:/Projects/Components.dll
    Assembly a = Assembly.LoadFrom( strURL );

    // assume strClassName is: MyProjects.MyComponents.Component1
    object   o = a.CreateInstance( strClassName );
}

Because the URL specifies the exact location of the file, Assemblies.LoadFrom is straightforward. However, Assemblies.Load is different and goes through the following process to resolve and load the assembly:

This process and the relevant elements shown above will be explained below.

Assembly Versioning

Version Number

Recall that an assembly version number is represented using the following format:

<Major>.<Minor>.<Build>.<Revision>

Two assemblies that differ by their version number are considered two different assemblies with respect to versioning. However, recall that versioning only applies to strongly-named assemblies. The following table shows the actual version numbers for common short cuts seen in the AssemblyVersion attribute:

AssemblyVersion Value Meaning
1 1.0.0.0
1.2 1.2.0.0
1.2.3 1.2.3.0
1.2.3.4 1.2.3.4
1.2.* 1.2.d.s
1.2.3.* 1.2.3.s
not present 0.0.0.0

Where d is the number of days since Feb 1, 2000 and s is the number of seconds since midnight / 2. 

Version Information

An assembly's version and the versions of its dependent assemblies are stored in the manifest. An assembly has two ways of expression its version information:

  1. The assembly's version number - along with the assembly's name and culture - is part of the assembly's identity This way of expressing version information is functional and is used by the runtime to enforce version policy.
  2. The other way is informational only and plays no part in the versioning policy. Here the version information is represented using the custom attribute System.Reflection.AssemblyInfomationalVersionAttribute.

Version Policy

Note these two important points regarding assembly versions:

  1. The default version policy at runtime for applications is that they will run only with the versions they were built with. This can be overridden by an explicit version policy in configuration files (the application's config file, the machine's config file, or the publisher policy file)
  2. Versioning is done only on assemblies with strong names.

For example, assume assembly A has version 1.0.0.1 and assembly B has version 1.0.0.1 and that assembly A lists assembly B as a dependent. If both assemblies have strong-names, then version 1.0.0.1 of A will only run with version 1.0.0.1 of B. If both assemblies do not have a strong name, then any version of A can run with any version of B. The situation where one assembly has a strong name and the other does not is not a valid scenario because strong-named assemblies require then dependent assemblies to have a strong-name

Mechanism

The runtime performs several steps when assembly A decides to call the dependent assembly B:

  1. Checks assembly A to determine the version of assembly B to be bound. 
  2. Checks all applicable configuration files to apply version policy. There are three kinds of version policies that are evaluated in the following order:
  3. Determines the correct assembly version to bind to based on the outcome of step 2
  4. Attempts to find the required version of the assembly in:

For example, step 1 may say that assembly A needs to call version 5 of assembly B (according to A's manifest). Step 2 however, may determine that the version policy requires version 6 of assembly B. Step 3 determines the required version to use is 6, and step 4 then searches for version 6 of assembly B. The discussion that follows relate to points 2 and 3 above where the correct assembly version is determined.

Therefore, the mechanism can be summarized as:

Determining the correct assembly version

As steps 2 and 3 above imply, the assembly resolver (a CLR component that locates the correct assembly) can map the requested version in step 1 to a newer or older version. The following shows how to create an assembly Version Policy within the application's configuration file:

<configuration>
    <runtime>

        <!-- 0 or 1 assemblyBinding element
        Contains information about assembly location and assembly version redirection -->
        <assemblyBinding>

            <!-- 0 or 1 probing element
            Specifies subdirectories below the application base direction that the CLR should search
            when loading assemblies -->
            <probing privatePath = "" />

            <!-- 0 or 1 publisherPolicy element. Specifies whether the runtime applies publisher policy -->
            <publisherPolicy apply="" />

            <!-- 0 to N dependentAssembly elements. Encapsulates binding policy and location for 
            each assembly -->
            <dependentAssembly>
                  <!-- self-explanator -->
                <assemblyIdentity name=""       publicKeyToken="" culture=""/>

                <!-- Specifies where the CLR can find the assembly. Once codebase per version-->
                <codeBase         version=""    href=""/>

                <!-- Redirect a specific version to another. This element can be repeated many times -->
                <bindingRedirect  oldVersion="" newVersion="" />

                <!-- Specifies whether the runtime applies publisher policy -->
                <publisherPolicy  apply=""/>
            </dependentAssembly>

        </assemblyBinding>
    </runtime>
</configuration>

The following code fragment redirects references to version 1.0.0.0 of a shared assembly called MyComponent.dll to version 2.0.0.0:

<configuration>
    <runtime>

        <assemblyBinding xmlns="urs:X">

            <dependentAssembly>
                <assemblyIdentity name="DataAccess"   publicKeyToken="1234abcd1234abcd" culture=""/>
                <bindingRedirect  oldVersion="1.0.0.0" newVersion="2.0.0.0" />
            </dependentAssembly>

        </assemblyBinding>

    </runtime>
</configuration>

In addition to version policies provided at the application level, the .NET Framework provides two other policy levels:  publisher and administrator:

Publisher-level policies

The three policy levels all have the same format described above, however a publisher policy even though it is specified as an XML configuration file, it is usually shipped as an assembly. There are three reasons that publisher policies are shipped as assemblies:

The flexibility obtained by decoupling compatibility statements from code makes it much easier to fix broken applications. If multiple versions of a publisher policy assembly are found in the assembly cache, the one with the highest version number will be used.

There are two steps to create a publisher policy assembly:

  1. Create the XML configuration file. Use the template above as a starting-point.
  2. Create an assembly containing the configuration file (use AL.EXE)

To create an assembly from a configuration file:

AL /link:MyComponentPolicy.xml /out:MyComponentPolicy2.0.0.0 /keyfile:MyComponent.snk /version:2.0.0.0 

Locating and loading the required version

Once the correct version has been determined, the CLR now attempts to locate and bind to the the required assembly. The term probing is often used when describing how the runtime attempts to locate assemblies. The runtime uses the following steps to resolve an assembly references:

  1. Determine the correct assembly version by examining applicable configuration files including the application configuration file, publisher policy file, and machine configuration file. This has been explained in detail in the sections above.
  2. Check whether the assembly name has been bound to before,  and of so use the previously loaded assembly.
  3. Check the Global Assembly Cache.
  4. Probe for the assembly.
Checking for previously referenced assemblies

If the requested assembly has also been requested in previous calls, the CLR will use the assembly that is already loaded.

Check the Global Assembly Cache

The binding process continues for strong-named assemblies by looking in the GAC. Note that the GAC is a secured resource that requires administrator rights to add and delete entries. Also note that the GAC can only contain signed assemblies with public keys.

Probe for the assembly

After the CLR determines the correct assembly version by looking in the assembly's manifest and then consulting the appropriate policy versions, and after looking in the GAC (for strong-named assemblies), the CLR attempts to find the assembly using the following order:

Example 1

[application base] / [assembly name].dll
[application base] / [assembly name] / [assembly name].dll


Example 2

[application base] / [culture] / [assembly name].dll
[application base] / [culture] / [assembly name] / [assembly name].dll

Example 3

[application base] / [binpath] / [culture] / [assembly name].dll
[application base] / [binpath] / [culture] / [assembly name] / [assembly name].dll

Example 4

[application base] / [binpath] / [assembly name].dll
[application base] / [binpath] / [assembly name] / [assembly name].dll

The following example shows the use of <probing> element which specifies where to find specific versions relative to the application's base directory:

<configuration>
    <runtime>

        <assemblyBinding xmlns="urs:X">
            <probing privatePath="bin;shared" />
        </assemblyBinding>

    </runtime>
</configuration>

Assembly resolution summary

The following diagram summarizes the assembly probing process:

Side-By-Side Execution

Side-by-side execution is the ability to run multiple versions of the same assembly simultaneously. For example, consider class DataAccess with has versions 1 and 2. Callers of DataAccess that express a dependency on version 1 will always get version 1 regardless of many subsequent version of DataAccess are installed on the computer.  Other callers that express no specific dependency can always get the latest version of DataAcess.

Support for side-by-side storage and side-by-side execution of different versions of the same assembly is built into the infrastructure of the runtime. Side-by-side storage can be accomplished via the use of the GAC. As for side-by-side execution, there are distinct types: