|
Versioning and Sharing
One of the primary causes of DLL Hell is the sharing model currently used in
component-based systems. By default, individual software components are shared
by multiple applications on the machine. For example, every time an installation
program copies a DLL to the system directory or registers a class in the COM
registry, that code will potentially have an effect on other applications
running on the machine. In particular, if an existing application used a
previous version of that shared component, that application will automatically
start using the new version. If the shared component is strictly backward
compatible this may be okay, but in many cases maintaining backward
compatibility is difficult, if not impossible. If backward compatibility is not
maintained, or cannot be maintained, this often results in applications that are
broken as a side effect of other applications being installed.
A principle design guideline in .NET is that of isolated components (or
assemblies). Isolating an assembly means that an assembly can only be accessed
by one application—it is not shared by multiple applications on the machine and
cannot be affected by changes made to the system by other applications.
Isolation gives a developer absolute control over the code that is used by his
application. Isolated, or application-private, assemblies are the default in
.NET applications. The trend toward isolated components started in Microsoft
Windows 2000 with the introduction of the .local file. This file was used to
cause both the OS Loader and COM to look in the application directory first when
trying to locate the requested component. (See the related article in the MSDN
Library, Implementing
Side-by-Side Component Sharing in Applications.)
However, there are cases where sharing an assembly between applications is
necessary. It clearly wouldn't make sense for every application to carry its own
copy of System.Windowns.Forms, System.Web or a common Web Forms control.
In .NET, sharing code between applications is an explicit decision.
Assemblies that are shared have some additional requirements. Specifically,
shared assemblies should support side by side so multiple versions of the same
assembly can be installed and run on the same machine, or even within the same
process, at the same time. In addition, shared assemblies have stricter naming
requirements. For example, an assembly that is shared must have a name that is
globally unique.
The need for both isolation and sharing leads us to think of two "kinds" of
assemblies. This is a rather loose categorization in that there are no real
structural differences between the two, but rather the difference is in how they
will be used: whether private to one application or shared among many.
Application-Private Assemblies
An application-private assembly is an assembly that is only visible to
one application. We expect this to be the most common case in .NET. The naming
requirements for private assemblies are simple: The assembly names must only be
unique within the application. There is no need for a globally unique name.
Keeping the names unique isn't a problem because the application developer has
complete control over which assemblies are isolated to the application.
Application-private assemblies are deployed within the directory structure of
the application in which they are used. Private assemblies can be placed
directly in the application directory, or in a subdirectory thereof. The CLR
finds these assemblies through a process called probing. Probing is
simply a mapping of the assembly name to the name of the file that contains the
manifest.
Specifically, the CLR takes the name of the assembly recorded in the assembly
reference, appends ".dll" and looks for that file in the application directory.
There are a few variants on this scheme where the Runtime will look in
subdirectories named by the assembly or in subdirectories named by the culture
of the assembly. For example, a developer may choose to deploy the assembly
containing resources localized to German in a subdirectory called "de" and to
Spanish in a directory called "es." (See the .NET Framework SDK Guide for
details.)
As just described, each assembly manifest includes version information about
its dependencies. This version information is not enforced for private
assemblies because the developer has complete control over the assemblies that
are deployed to the application directory.
Shared Assemblies
The .NET Framework also supports the concept of a shared assembly. A
shared assembly is one that is used by multiple applications on the machine.
With .NET, sharing code between applications is an explicit decision. Shared
assemblies have some additional requirements aimed at avoiding the sharing
problems we experience today. In addition to the support for side by side
describe earlier, shared assemblies have much more stringent naming
requirements. For example, a shared assembly must have a name that is globally
unique. Also, the system must provide for "protection of the name"—that is,
preventing someone from reusing another's assembly name. For example, say you're
a vendor of a grid control and you've released version 1 of your assembly. As an
author you need assurance that no one else can release an assembly claiming to
be version 2 or your grid control. The .NET Framework supports these naming
requirements through a technique called strong names (described in detail
in the next section).
Typically, an application author does not have the same degree of control
over the shared assemblies used by the application. As a result, version
information is checked on every reference to a shared assembly. In addition, the
.NET Framework allows applications and administrators to override the version of
an assembly that is used by the application by specifying version policies.
Shared assemblies are not necessarily deployed privately to one application,
although that approach is still viable, especially if xcopy deployment is a
requirement. In addition to a private application directory, a shared assembly
may also be deployed to the Global Assembly Cache or to any URL as long as a
codebase describing the location of the assembly is supplied in the
application’s configuration file. The global assembly cache is a machine-wide
store for assemblies that are used by more than one application. As described,
deploying to the cache is not a requirement, but there are some advantages to
doing so. For example, side-by-side storage of multiple versions of an assembly
is provided automatically. Also, administrators can use the store to deploy bug
fixes or security patches that they want every application on the machine to
use. Finally, there are a few performance improvements associated with deploying
to the global assembly cache. The first involves the verification of strong name
signatures as described in the Strong Name section below. The second performance
improvement involves working set. If several applications are using the same
assembly simultaneously, loading that assembly from the same location on disk
leverages the code sharing behavior provided by the OS. In contrast, loading the
same assembly from multiple different locations (application directories) will
result in many copies of the same code being loaded. Adding an assembly to the
cache on an end user's machine is typically accomplished using a setup program
based on the Windows Installer or some other install technology. Assemblies
never end up in the cache as a side effect of running some application or
browsing to a Web page. Instead, installing an assembly to the cache requires an
explicit action on the part of the user. Windows Installer 2.0, which ships with
Windows XP and Visual Studio .NET, has been enhanced to fully understand the
concept of assemblies, the assembly cache and isolated applications. This means
you will be able to use all of the Windows Installer features, such as on-demand
install and application repair, with your .NET applications.
It’s often not practical to build an install package every time you want to
add an assembly to the cache on development and test machines. As a result, the
.NET SDK includes some tools for working with the assembly cache. The first is a
tool called gacutil that allows you to add assemblies to the cache and remove
them later. Use the /i switch to add an assembly to the cache:
gacutil /i:myassembly.dll
See the .NET Framework SDK documentation for a full description of the
options supported by gacutil.
The other tools are a Windows Shell Extension that allows you to manipulate
the cache using the Windows Explorer and the .NET Framework Configuration Tool.
The Shell Extension can be accessed by navigating to the "assembly" subdirectory
under your Windows directory. The .NET Framework Configuration Tool can be found
in the Administrative Tools section of the Control Panel.
Strong names
Strong names are used to enable the stricter naming requirements associated
with shared assemblies. Strong names have three goals:
- Name uniqueness. Shared assemblies must have names that are globally
unique.
- Prevent name spoofing. Developers don't want someone else releasing a
subsequent version of one of your assemblies and falsely claim it came from you,
either by accident or intentionally.
- Provide identity on reference. When resolving a reference to an
assembly, strong names are used to guarantee the assembly that is loaded came
from the expected publisher.
Strong names are implemented using standard public key cryptography. In
general, the process works as follows: The author of an assembly generates a key
pair (or uses an existing one), signs the file containing the manifest with the
private key, and makes the public key available to callers. When references are
made to the assembly, the caller records the public key corresponding to the
private key used to generate the strong name. Figure 5 outlines how this process
works at development time, including how keys are stored in the metadata and how
the signature is generated.
The scenario is an assembly called "Main," which references an assembly
called "MyLib." MyLib has a shared name. The important steps are described as
follows. |