Focus on Microsoft Technologies - Tutorials, Articles, Code Samples.

Tuesday, August 22, 2006

Solve DLL Hell in .NET Framework

The Microsoft® .NET Framework introduces several new features aimed at simplifying application deployment and solving DLL Hell. Both end users and developers are familiar with the versioning and deployment issues that can arise with today's component-based systems. For example, virtually every end user has installed a new application on their machine, only to find that an existing application mysteriously stops working. Most developers have also spent time with Regedit, trying to keep all the necessary registry entries consistent in order to activate a COM class.
The design guidelines and implementation techniques used in the .NET Framework to solve DLL Hell are built on the work done in Microsoft Windows® 2000, as described by Rick Anderson in The End of DLL Hell, and by David D'Souza, BJ Whalen, and Peter Wilson in Implementing Side-by-Side Component Sharing in Applications (Expanded). The .NET Framework extends this previous work by providing features including application isolation and side-by-side components for applications built with managed code on the .NET platform. Also, note that Windows XP provides the same isolation and versioning features for unmanaged code, including COM classes and Win32 DLLs (see How To Build and Service Isolated Applications and Side-by-Side Assemblies for Windows XP for details).
This article introduces the concept of an assembly and describes how .NET uses assemblies to solve versioning and deployment problems. In particular, we'll discuss how assemblies are structured, how they are named, and how compilers and the Common Language Runtime (CLR) use assemblies to record and enforce version dependencies between pieces of an application. We'll also discuss how applications and administrators can customize versioning behavior through what we call version policies.
After assemblies are introduced and described, several deployment scenarios will be presented, providing a sampling of the various packaging and distribution options available in the .NET Framework.
Problem Statement
From a customer perspective, the most common versioning problem is what we call DLL Hell. Simply stated, DLL Hell refers to the set of problems caused when multiple applications attempt to share a common component like a dynamic-link library (DLL) or a Component Object Model (COM) class. In the most typical case, one application will install a new version of the shared component that is not backward compatible with the version already on the machine. Although the application that has just been installed works fine, existing applications that depended on a previous version of the shared component might no longer work. In some cases, the cause of the problem is even more subtle. For example, consider the scenario where a user downloads a Microsoft ActiveX® control as a side effect of visiting some Web site. When the control is downloaded it will replace any existing versions of the control that were present on the machine. If an application that has been installed on the machine happens to use this control, it too might potentially stop working.
In many cases there is a significant delay before a user discovers that an application has stopped working. As a result, it is often difficult to remember when a change was made to the machine that could have affected the app. A user may remember installing something a week ago, but there is no obvious correlation between that installation and the behavior they are now seeing. To make matters worse, there are few diagnostic tools available today to help the user (or the support person who is helping them) determine what is wrong.
The reason for these issues is that version information about the different components of an application aren't recorded or enforced by the system. Also, changes made to the system on behalf of one application will typically affect all applications on the machine—building an application today that is completely isolated from changes is not easy.
One reason why it's hard to build an isolated application is that the current run-time environment typically allows the installation of only a single version of a component or an application. This restriction means that component authors must write their code in a way that remains backward compatible, otherwise they risk breaking existing applications when they install a new component. In practice, writing code that is forever backward compatible is extremely difficult, if not impossible. In .NET, the notion of side by side is core to the versioning story. Side by side is the ability to install and run multiple versions of the same component on the machine at the same time. With components that support side-by-side, authors aren't necessarily tied to maintaining strict backward compatibility because different applications are free to use different versions of a shared component.
Deployment and Installation
Installing an application today is a multi-step process. Typically, installing an application involves copying a number of software components to the disk and making a series of registry entries that describe those components to the system.
The separation between the entries in the registry and the files on disk makes it very difficult to replicate applications and to uninstall them. Also, the relationship between the various entries required to fully describe a COM class in the registry is very loose. These entries often include entries for coclasses, interfaces, typelibs, and DCOM app IDs, not to mention any entries made to register document extensions or component categories. Oftentimes you end up keeping these in sync manually.
Finally, this registry footprint is required to activate any COM class. This drastically complicates the process of deploying distributed applications because each client machine must be touched to make the appropriate registry entries.
These problems are primarily caused by the description of a component being kept separate from the component itself. In other words, applications are neither self-describing nor self-contained.
Characteristics of the Solution
The .NET Framework must provide the following basic capabilities to solve the problems just described:
Applications must be self-describing. Applications that are self-describing remove the dependency on the registry, enabling zero-impact installation and simplifying uninstall and replication.
Version information must be recorded and enforced. Versioning support must be built into the platform to ensure that the proper version of a dependency gets loaded at run time.
Must remember "last known good." When an application successfully runs, the platform must remember the set of components—including their versions—that worked together. In addition, tools must be provided that allow administrators to easily revert applications to this "last known good" state.
Support for side-by-side components. Allowing multiple versions of a component to be installed and running on the machine simultaneously allows callers to specify which version they'd like to load instead of a version "forced" on unknowingly. The .NET Framework takes side by side a step farther by allowing multiple versions of the framework itself to coexist on a single machine. This dramatically simplifies the upgrade story, because an administrator can choose to run different applications on different versions of the .NET Framework if required.
Application isolation. The .NET Framework must make it easy, and in fact the default, to write applications that cannot be affected by changes made to the machine on behalf of other applications.
Assemblies: The Building Blocks
Assemblies are the building blocks used by the .NET Framework to solve the versioning and deployment issues just described. Assemblies are the deployment unit for types and resources. In many ways an assembly equates to a DLL in today's world; in essence, assemblies are a "logical DLLs."
Assemblies are self-describing through metadata called a manifest. Just as .NET uses metadata to describe types, it also uses metadata to describe the assemblies that contain the types.
Assemblies are about much more than deployment. For example, versioning in .NET is done at the assembly level—nothing smaller, like a module or a type, is versioned. Also, assemblies are used to share code between applications. The assembly that a type is contained in is part of the identity of the type.
The code access security system uses assemblies at the core of its permissions model. The author of an assembly records in the manifest the set of permissions required to run the code, and the administrator grants permissions to code based on the assembly in which the code is contained.
Finally, assemblies are also core to the type system and the run-time system in that they establish a visibility boundary for types and serve as a run-time scope for resolving references to types.
Assembly Manifests
Specifically, a manifest includes the following data about the assembly:
Identity. An assembly's identity consists of four parts: a simple text name, a version number, an optional culture, and an optional public key if the assembly was built for sharing (see section on Shared Assemblies below).
File list. A manifest includes a list of all files that make up the assembly. For each file, the manifest records its name and a cryptographic hash of its contents at the time the manifest was built. This hash is verified at run time to ensure that the deployment unit is consistent.
Referenced assemblies. Dependencies between assemblies are stored in the calling assembly's manifest. The dependency information includes a version number, which is used at run time to ensure that the correct version of the dependency is loaded.
Exported types and resources. The visibility options available to types and resources include "visible only within my assembly" and "visible to callers outside my assembly."
Permission requests. The permission requests for an assembly are grouped into three sets: 1) those required for the assembly to run, 2) those that are desired but the assembly will still have some functionality even if they aren't granted, and 3) those that the author never wants the assembly to be granted.
The IL Disassembler (Ildasm) SDK tool is useful for looking at the code and metadata in an assembly. Figure 1 is an example manifest as displayed by Ildasm. The .assembly directive identifies the assembly and the .assembly extern directives contain the information about other assemblies on which this one depends.

Find More

Post a Comment