Introducing the assembly--a cure for 'DLL Hell'?

With Microsoft .NET, developers have a new type of code library, the assembly. Learn about the new versioning features of these "logical DLLs," and see how they may solve DLL Hell.

Windows applications often succumb to a common malady known as "DLL Hell," where a component of one application is overwritten by an incompatible component of another application, causing the first application to stop functioning correctly. These problems can be difficult to diagnose, as they may not become evident until some time after the offending component was installed. Visual Basic apps are particularly notorious for DLL Hell because they tend to have more external dependencies than applications developed in other languages. Microsoft's .NET initiative promises to help alleviate this problem with a new distribution unit called an assembly.

Assemble on the lawn
From a programming standpoint, an assembly is functionally identical to a Java package: It provides a distributable library of related classes and defines their scope. For those that don't speak Java, assemblies are to .NET as DLL files are to COM development—except that an assembly can be composed of multiple files.

One property of assemblies that makes them superior to DLLs is that assemblies are self-documenting, via what's known as a manifest. The manifest is included in the assembly itself and contains metadata describing the exported classes in the assembly, any external dependencies those classes require, the permissions required to use the assembly, and versioning information about any dependencies. In .NET, assemblies provide the least common denominator for versioning; classes and individual files cannot be versioned. This prevents the possibility of having to examine multiple files to determine the version of a component installed on a system, a pet peeve of mine. .NET's versioning features promise to do the most to eliminate the DLL Hell syndrome, as you'll see in a little bit.

Private by birth, public by the grace of God
By default, .NET assemblies are private, meaning that they can only be used by one application. Private assemblies should be installed into the application's folder or one of its subfolders. Microsoft expects that most .NET development will center around private assemblies—in fact, it encourages it.

One potential problem here is that users' hard drives could become cluttered with multiple copies of the same private assemblies, which, ironically enough, sounds to me a lot like one problem COM was supposed to help solve. Microsoft's response to this criticism thus far has been along the lines of, "Hard drives are cheap; buy a bigger one." Fortunately, it's relatively easy to make a private assembly public; no recompiles or code editing are needed.

Sharing is good
The primary difference between shared and private assemblies is that the former are usually kept in the appropriately named Global Assembly Cache or GAC. (Yes, that is indeed pronounced gack.) The GAC is sometimes referred to as the Global Assembly Store in Microsoft's documentation, probably because the latter results in a better acronym.

The GAC can store multiple versions of the same assembly, a capability Microsoft calls "side by side deployment." This ability virtually eliminates the possibility of one application breaking another by installing an incompatible shared component, which has to be good news for developers.

A client assembly can specify which version or versions of a shared assembly it is compatible with. The .NET runtime will not load any server assemblies if a client's version requirements can’t be satisfied. By the way, assembly versions in .NET are identified by four-digit numbers in the format Major.Minor.Build.Revision.

By default, an assembly is considered to be compatible only with a shared assembly having the same major and minor version numbers as the assembly it was compiled against. The runtime loader will, however, preferentially load an assembly with a higher build or revision number than the client's preferred version, if one exists, to automatically provide support for quick fixes in a component. This default behavior isn't always what you'd want, so a programmer or system administrator can define custom versioning policies that, for example, force the loading of a particular version or disable the loader's preference for assemblies with higher build or revision numbers.

As of Beta 1, the versioning policy is contained in XML files that reside either in the application directory or the Windows directory. These files define application-specific and system-wide versioning policies, respectively.

What's in a name?
You can name a private assembly essentially whatever you want, as long as that name is unique within your application. Public assemblies, on the other hand, are required to have some kind of globally unique identifier so that the .NET runtime can identify them. Gone are the Class IDs and Prog IDs of COM development. Shared assemblies are instead identified by what's known as a strong name. Strong names are derived by the use of standard public-key cryptography: The developer "signs" the assembly with a private key and provides a public key to be used by client assemblies. The public key then becomes part of the assembly's strong name.

Signing an assembly using a command-line compiler requires the use of several command-line switches, so it can be an onerous task. Fortunately, Visual Studio.NET will take care of this automatically for the programmer.

Big things come in small assemblies
Microsoft has finally recognized the scope of the DLL Hell problem and seen that it can be remedied only by policies enforced at the operating system level. It appears as though Microsoft is on the right track, having worked around the registry hacking and single component support that plagued COM applications. In fact, it's possible to install a .NET application with a simple XCopy!

Editor's Picks