Versioning in the Compact Framework

One of the truly revolutionary aspects of the .NET Framework and, thus, of the Compact Framework is the enhanced support for versioning and code sharing. Unlike previous unmanaged environments such as COM, in which a component was automatically published to the systemwide registry, the managed world supports both private and shared components, as well as a robust side-by-side versioning system.

Private Assemblies

graphics/key point_icon.gif

In one key respect, the code-sharing model of the Compact Framework inverts that used by managed environments, such as COM, as exposed through VB 6.0. In the Compact Framework, components (assemblies) that are built and subsequently referenced by other projects are by default private assemblies. In other words, VS .NET by default copies the referenced assembly into the bin directory of the calling project, thereby creating a private copy of the assembly for each application in which it is referenced. This is most easily seen in the Properties window in VS .NET, in which the Copy Local property is set to True for private assemblies.

When the calling project is compiled, the resulting assembly contains a manifest, which includes, among other things, the name of the assemblies that it has referenced, along with their version numbers, referred to as the assembly bindings. The manifest can be viewed using the IL Disassembler utility (ILDasm.exe), which ships with the .NET Framework, as shown in Figure 10-1. At runtime the EE loads a referenced assembly before JIT compiles the first method that refers to it. For private assemblies, the version number is not consulted, and so as long as the referenced assembly exists in the bin directory, it will be loaded and its methods invoked if possible. Of course, if a method in a referenced assembly has its signature changed (by changing the return or parameter types, for example), a runtime error such as MissingMethodException will be thrown.

Figure 10-1. Assembly Binding. This screen shot shows the manifest of a smart device application using the ILDasm utility. The manifest contains the references to other assemblies. For private assemblies (such as SmartDeviceApplication2 in this case), the name of the assembly and its version number are recorded.

graphics/10fig01.jpg

NOTE

The Compact Framework does not support precompiling assemblies to native code through the use of the Native Image Generator (NGen.exe) utility, as the desktop Framework does. This utility is used in the .NET Framework to bypass the JIT compilation and, therefore, to speed the load time of the assembly.


This feature of the Compact Framework, inherited from the desktop Framework, allows great flexibility by allowing developers to update an application simply by deploying an updated assembly in the application's bin directory, an example of which we'll show later in this chapter, and not having to worry about registration. It also promotes application isolation so that changes to one assembly needn't affect other applications installed on the device. This addresses the classic problem of installing a new DLL on a device and inadvertently breaking existing applications.

Shared Assemblies

Although the default behavior of the Compact Framework is to use private assemblies, developers can optionally create shared assemblies that can simultaneously be referenced by multiple applications on the same device. In this way, an updated assembly distributed with a single application can automatically be used by other applications on the device. To create a shared assembly, because sharing is not the default it is in the unmanaged world, a developer must create a strong name at build time and place the assembly in the GAC on the device.

Creating a Strong Name

Simply put, a strong name is information placed into an assembly at compile time that provides name uniqueness, version compatibility, and binary integrity to the shared assembly. It does this by placing a public-key token (called the originator) and a digital signature in the assembly, along with the assembly's identity information (assembly name, version number, and culture or locale information). This information is then used by the EE at binding time to make sure that the correct assembly is loaded and that the assembly has not been altered since it was compiled.[1] In the desktop Framework this verification actually occurs when the assembly is placed in the GAC, instead of at binding or load time.

[1] The verification processed is performed by using the digitial signature that is embedded in the assembly.

Because referencing an assembly that has a strong name implies that you want to derive the benefits of strong names, assemblies that use them can reference only other strong-named assemblies. In other words, if you create a class library assembly that relies on code in another assembly, that other assembly must also have a strong name. For this reason, it is a best practice to create strong names for assemblies that contain base-level or common functionality and may be used from a variety of other assemblies. For example, the assemblies that comprise the Compact Framework have strong names that allow them to be referenced from any other assembly, including those with strong names.

To create a strong name for the assembly, just as in the desktop Framework, a developer adds attributes to the AssemblyInfo.cs (or .vb) file. For example, to reference the file that contains the key pair used to generate the public-key token and digital signature, to set the version number, and to identify the culture to be used, the AssemblyInfo.cs file might contain the following attributes:


[assembly:AssemblyKeyFile("AtomicUtils.dat")]
[assembly:AssemblyVersion("1.0.*")]
[assembly:AssemblyCulture ("")]

In this case, the attributes point to the AtomicUtils.dat file, specify the four-part version number as 1.0, followed by two autogenerated values, and indicate that the assembly contains culture-neutral resources (the default as discussed in Chapter 8).

The AssemblyKeyFile attribute points to a file that contains a key pair generated by the Strong Name (Sn.exe) utility that ships with the .NET Framework. This key pair is assured to be distinct and so provides the name uniqueness guaranteed by shared assemblies. Alternatively, the AssemblyKeyName attribute can be used to point to a key pair stored on the developer's machine in the key store provided by the CryptoAPI. Although the desktop Framework also supports the concept of delayed signing through the AssemblyDelaySign attribute, the Compact Framework does not.[2]

[2] Delayed signing is used by organizations to centralize the process of applying the cryptographic information to the assembly and to protect the private key used in the process. When the AssemblyDelaySign attribute is used, the compiler leaves space in the assembly for the signature, which is then applied later and by a centralized group with access to the private key using the Strong Name utility.

graphics/key point_icon.gif

The version number is especially important in the Compact Framework because it helps to enforce strict binding for shared assemblies. In other words the four-part number (major.minor.build.revision) for the referenced assembly must match that found in the calling assembly's manifest. Although this behavior can be overridden in the desktop Framework through binding information placed in an assembly configuration file, as mentioned in previous chapters, the Compact Framework does not support configuration files or applying this type of dynamic binding. As a result, developers will need to recompile the calling assembly if it is to use a new version of a shared assembly.

TIP

Because the AssemblyVersion attribute can automatically generate parts of the four-part version number, it is a best practice to specify explicitly the version number for assemblies that are to be placed in the GAC. This gives developers more control over the binding process.


Finally, the AssemblyCulture attribute specifies which culture the assembly supports. In fact, this is the attribute that is applied to satellite assemblies that contain resources discussed in Chapter 8. For example, if the attribute contains the German culture "de," the manifest of the assembly would contain the following declaration:


.assembly MyAssembly
{
  .hash algorithm 0x00008004
  .ver 1:0:1174:22525
  .locale = (64 00 65 00 00 00 )       // d.e...
}

By specifying an empty string, the assembly is assumed to contain executable code and culture-neutral resources. As a result, this attribute should be used only for satellite assemblies.

When a shared assembly is referenced by a calling application, its manifest will contain the assembly name, public-key token, version, and culture; therefore, it will ensure that at runtime the caller will bind only to the specific version of the assembly referenced at design time. In addition, by default, the Copy Local property in VS .NET will be set to False when referencing a strong-named assembly.

NOTE

It is important to remember that the EE finds and loads the appropriate assemblies at runtime and, therefore, at design time it doesn't matter where on the developer's workstation a shared assembly is referenced from; however, it is a best practice to group shared assemblies in a folder and deploy that folder to all of the developer's workstations to provide a common way of referencing shared assemblies. Of course, developers could also access read-only copies of the shared assemblies from a source-code control system, such as Visual SourceSafe.


Using the GAC

Because VS .NET sets the Copy Local property to False for shared assemblies, the application needs to be able to find the assembly at runtime. This is the role of the GAC, which acts as a registry for shared components and allows more than one version of the same component to exist side-by-side on the device. At runtime, the EE first searches for shared assemblies in the GAC. This behavior is the same as that for the desktop Framework; however, the plumbing used by the Compact Framework to handle the GAC is quite different.

On the device, the GAC is located in the \Windows directory and simply consists of the assemblies renamed with the prefix "GAC" and appended with the version number and culture, such as GAC_System.Data.Common_ v1_0_5000_0_cneutral_1.dll. Assemblies are placed into the GAC by the Cgacutil.exe utility invoked by the EE each time a Compact Framework application is executed. This utility looks for files with a .gac extension in the \Windows directory that contain lists of shared assemblies to be placed into the GAC.

The .gac file is an ANSI or UTF-8-encoded text file sent to the \Windows directory during the application's deployment, as discussed later in this chapter.[3] A typical .gac file might look as follows:

[3] The Compact Framework installs a .gac file in the \Windows directory that includes the Compact Framework core assemblies.


\Program Files\Atomic\AtomicUtils.dll
\Program Files\Atomic\AtomicData.dll

Consequently, if the .gac file is deleted from the \Windows directory or is updated, the appropriate changes (insertions and deletions) will be made to the GAC the next time a Compact Framework application is executed.

Benefits

graphics/key point_icon.gif

There are several benefits to using the GAC in your solutions. First, it allows multiple applications to reference the same shared assembly on the device, thereby not taking up the added storage required for private copies on an already resource-constrained device. Second, it allows changes to be made to the shared assembly that all applications referencing the assembly can immediately take advantage of. And, third, the ability to place assemblies in the GAC allows developers to install multiple versions of the assembly on the device and let each application bind to the correct one.

As mentioned previously, the Compact Framework does not support configurable or dynamic version policy, and so, the version of the assembly that an application was compiled against will always be the one that it binds to at runtime.