Assemblies and Deployment

I l @ ve RuBoard

One of the most frustrating aspects of Windows-based applications in the past has been the problem of "DLL hell." To promote reuse and allow for the upgrading of applications without completely reinstalling them, developers have made use of shared DLLs. Windows itself comprises a collection of DLLs containing many of the core operating system routines and functions. The idea was that developers could invoke a well-written piece of code packaged in a DLL in much the same way that they would invoke an ordinary procedure. At run time, the DLL would be located and loaded into the calling process's address space and the required procedure would be executed. The dynamic nature of a DLL allowed it to be shared by a number of processes simultaneously , reducing the overall memory requirements of those processes. A DLL could also be overwritten with a later release, perhaps fixing a bug or two, adding new procedures, or changing an existing procedure to use some new, faster algorithm. Applications using the DLL would automatically pick up and use this new version without being aware that anything had changed.

But then the fun would begin!

The Joy of DLLs: The Movie

The joy of DLLs is that they can be replaced dynamically. And the trouble with DLLs is that they can be replaced dynamically! Consider the following screenplay concerning the intrigue sparked by a typical application (definitely not coming to a movie screen near you):

Scene 1: A small dark room in Upper Gumtree. A developer named Honest John writes an application that makes use of one of the common redistributable DLLs found in Windows: CTL3D32.dll (the 3-D controls). He assumes that the DLL might not always be present on the user 's machine, so he makes sure that the application installation package includes the DLL and installs it along with the application.

Scene 2: A plush development lab in Redmond. A developer at Microsoft uncovers a bug in the current release of CTL3D32.dll. The development team does the honorable thing and fixes the bug, making it available for download and redistribution. They also take the opportunity to add some extra functionality that the Windows community at large has been calling for ”a few new procedures and a couple of enhancements to some of the controls. After a while, this becomes the "standard" version of the DLL; all new applications use this updated DLL, and many make use of the new features.

Do you follow the plot so far? Good.

Scene 3: An unsuspecting user's desktop computer. The user has just taken delivery of a brand-new desktop computer with all the latest software. Everything works well ”Tetris in 32-bit color has never looked so good! Then the user installs Honest John's application (which uses CTL3D32.dll). As the installation progresses, the user suddenly see the message "Target file CTL3D32.dll exists and is newer than the source. Copy anyway?" She thinks hard, has a cup of coffee, and then clicks Yes. What could possibly go wrong? The application installation finishes, and she runs it to test it ”it works fine.

Scene 4: The user's computer on deadline day. It is Friday and the weekend beckons. The user has to process the current week's sales and then go home. She fires up the sales processing application, which pops up an error message and won't let the sales processing application run. She tries again, with the same result. Three hours later, after several hundred more attempts to run the application, all with the same result, and a long call to the help desk of the company that supplied the computer in the first place (which yields no help), the user gives up and throws the computer out the window. The reason for the failure? The sales processing application was built using a later version of CTL3D32.dll than that installed with Honest John's application.

We admit that we might have taken some dramatic license here, but we're sure you can empathize with the unsuspecting user. The key problem is that a DLL was overwritten with an earlier, less functional version. Wouldn't it be nice if you could have multiple versions of the same DLL installed on the same computer at the same time, and have applications automatically use the correct version? This ability is one of the major features of .NET and is achieved using versioned assemblies.

.NET Assemblies

We used the word assembly when we discussed MSIL, and we said that an assembly was like a DLL. That was only partly true. An assembly is more like a logical DLL that can comprise several physical files, each of which is called a module . An assembly is the unit of deployment and code access security used by .NET.

You can compile individual source code files into .NET modules using the appropriate language compiler. For example, you can use the /target:module switch with vjc. You can then combine them in an assembly using the Assembly Linker tool (AL.exe, located in \Windows\Microsoft.NET\Framework\ <version> ). Alternatively, most compilers will let you compile and link multiple source files in an assembly without your needing to build separate modules and use the Assembly Linker ”it depends on the compiler.

When any module is compiled, the PE file that is generated contains a description of the module's contents. This description is referred to as metadata. Tools such as the Assembly Linker use the metadata exposed by modules to determine how to link them together. The difference between a module and an assembly is that an assembly also contains a manifest . The manifest holds additional information (version, load address, internal flags, and so on) about the assembly and about any dependencies on other assemblies or other resources (such as bitmaps, cursors , and icons). You can use ILDASM to examine the manifest of an assembly, as you saw earlier.

When you compile an application that uses the classes and methods in one or more assemblies, you use the /references option to indicate which assembly or assemblies are required. The compiler will locate each assembly and examine the metadata for the methods and types they contain, checking to make sure that they satisfy those needed by the application. If there are any unresolved references, the application will not compile.

For example, the following code (SizeCake.jsl) shows a J# client application that uses the CakeUtils package to determine how many people a 10-inch sponge cake will feed. (You saw the Visual Basic version of this class earlier.)

SizeCake.jsl
 packageSizeCake; importCakeUtils.*; importSystem.*; publicclassSizeTest { publicstaticvoidmain(String[]args) { shortnumEaters; //Howmanypeoplewilla10" hexagonalspongecakefeed? numEaters=CakeInfo.FeedsHowMany((short)10,CakeShape.Hexagonal, CakeFilling.Sponge); Console.WriteLine("Thiswillfeed " +numEaters+ " people"); } } 

The CakeUtils package was previously compiled in an assembly called CakeUtils.dll. To compile the SizeCake application, you use the following command:

 vjcSizeCake.jsl/reference:CakeUtils.dll 

The result of this command is an executable, SizeCake.exe, which is also an assembly. If you use ILDASM to examine it and look at its manifest, you'll see the reference to the CakeUtils assembly, as in Figure 2-7:

Figure 2-7. The SizeCake assembly manifest

Bear in mind that this is just a logical link. The SizeCake assembly simply contains a reference to the CakeUtils assembly. At run time, the common language runtime will locate and load the CakeUtils assembly dynamically as and when required.

Single-File Assemblies

The simplest applications that use no any additional assemblies other than the standard runtime (mscorlib.dll and, for J# applications, vjslib.dll and vjscor.dll) are compiled in an EXE file without use of the references option. These are referred to as self-contained or single-file assemblies. The Greetings program you saw at the start of this chapter was an example of such an application.

Private Assemblies

When you run an application, the common language runtime will locate and load any assemblies that are needed. This process is known as binding . The common language runtime uses a specific search algorithm to locate assemblies, which will be described shortly, in the section "Configuring an Application." The simplest way to make sure the runtime is successful is to use private assemblies. In this configuration, the assemblies used by your application are copied to the folder containing the application's EXE file when the application is installed. Alternatively, the runtime can also look in subfolders named after each assembly. You can additionally specify a private binpath using an application configuration file. (More on this later.)

For example, you could use the directory structure shown in Figure 2-8 for the SizeCake application. The SizeCake.exe program is located in the SizeCake folder, which also contains a subfolder named CakeUtils, which contains CakeUtils.dll.

Figure 2-8. SizeCake application directories

The binding mechanism of the runtime requires only that it can locate the assemblies needed by the application. It does not rely on any settings in the Windows Registry. Application installation is therefore easy ”you simply copy the EXE and the DLLs onto the computer. This is known as XCOPY deployment (you can use the XCOPY command to copy directory structures). Uninstallation is equally easy ”you delete the folder and subfolders that contain the application and its assemblies. You won't leave any ghosts behind in the Registry that will come back to haunt you later!

Private assemblies are fine, and because they're private to your application they will not be accidentally overwritten if another application uses a later, incompatible release of the same assembly. Both applications will have their own private copies of the assembly and will not interfere with one another. However, if you have limited disk space, you might not want to have 1001 copies of the same assembly installed on your computer. You also have to consider maintenance issues. Suppose you actually want to replace an assembly with a later version because the developers have found and corrected a bug. Hunting down every copy of the assembly and replacing it with the fixed version would be time consuming. Handily, the runtime provides some different places to store assemblies.

The Global Assembly Cache

One place you can store assemblies is the Global Assembly Cache (GAC). The GAC is a set of system folders managed by the runtime, and it allows multiple applications to share assemblies. The current location of the GAC is \WINDOWS\assembly, although this site may change in future versions of .NET. The simplest way to examine the GAC is to use Windows Explorer.

The GAC is an intricate structure consisting of folders and subfolders that can hold many different versions of the same assembly. The GAC Viewer (shown in Figure 2-9), which is displayed when you navigate to the \WINNT\assembly folder, is a shell extension that is installed with .NET. It presents the contents of the GAC in a more friendly way. If you want to look at the raw GAC, you can open a command prompt window and examine it from the command line by moving around the \WINDOWS\Assembly subfolders. Don't change anything, though!

Figure 2-9. The GAC Viewer

Strong Names and Version Numbers

You can place your own assemblies in the GAC, but they must first meet a couple of requirements. The first condition is that an assembly must have a strong name . This is a name that uniquely distinguishes your assembly from any other. The earlier examples used the filename of an assembly to identify it. This is fine for private assemblies where there is little scope for ambiguity, but filename uniqueness is not guaranteed once you start using the GAC ”two developers might use the same name for two different assemblies. A strong name combines the filename with version information, culture information (you can provide a different build of the assembly for different cultures ”a Japanese version, for example), a public key, and a digital signature. Strong names not only ensure that your assemblies are unique, but they also add a degree of protection to your code through public/private key encryption. If you reference a strongly-named DLL, you can be sure where it originated and that it has not been tampered with. It could still be malicious, but at least you know who to sue!

The .NET Framework SDK provides the strong name utility, sn.exe, in the folder \Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin, for generating public/private key pairs. The sn utility creates a file containing the keys if you use the “ k option. The key file is conventionally given the SNK extension, for Strong Name Keys :

 snkCakeUtilsKeys.snk 

You can indicate that your assembly should be signed with the keys in this file using some predefined assembly attributes. The AssemblyKeyFileAttribute attribute specifies the key file (CakeUtilKeys.snk), and the AssemblyVersionAttribute attribute should be set to the version number for your assembly. Attributes in J# are added using Javadoc-style comments. The J# compiler will pick them out and process them. Notice that you must import the System.Reflection package in order to use these attributes:

 packageCakeUtils; importSystem.Reflection.*; /**@assemblyAssemblyVersionAttribute("1.0.*")*/ /**@assemblyAssemblyKeyFileAttribute("CakeUtilsKeys.snk")*/ 

Note

If you use Visual Studio .NET to write your assembly, it will create a file called AssemblyInfo.jsl and add it to your project automatically. This file contains placeholders where you can insert values for the AssemblyVersionAttribute and AssemblyKeyFileAttribute attributes (among others).


The second condition for using the GAC is that your assembly must have a version number. The versioning scheme used by .NET has four parts :

 <MajorVersion>.<MinorVersion>.<BuildNumber>.<Revision> 

You must specify a major and minor version number for your assembly, but the compiler can automatically generate a Build Number and Revision every time you rebuild the assembly. You simply specify * for the build number and revision. When an application is linked to a particular assembly using the /references option, the strong name and version number of the assembly are noted and stored in the manifest of the application.

A sample manifest is shown in Figure 2-10. You can see the strong name and version number of the assembly using ILDASM. At run time, the runtime will make sure the correct version is loaded. To make sure the correct assembly is used (and not the correct version of the wrong assembly that has the same filename), the runtime also uses the publickeytoken value specified in the manifest of the consuming application. This is a hexadecimal version of the strong name of the required assembly.

Figure 2-10. The manifest of SizeCake.exe

When the application runs, the runtime will search for required strongly named assemblies in the GAC. (Assemblies without strong names cannot be held in the GAC and will most likely be deployed as private assemblies.) If any assemblies are missing, the runtime will look for them elsewhere using application configuration files or will probe for them under the application's base directory using the same strategy it uses to search for a private assembly.

Deploying to the GAC

You can add an assembly to the GAC in at least two ways. The first and simplest way is to use Windows Explorer to drag and drop the DLL comprising your assembly into the \WINDOWS\assembly folder. The GAC Viewer will create the appropriate directory structure and put your assembly in the correct place. You'll see the assembly appear in the GAC, together with its version number and public key token. You can remove an assembly from the GAC by clicking it and pressing the Delete key.

Warning

Never use the copy command from the command line to put assemblies into the GAC. Deploying an assembly to the GAC is more involved than just copying a file. For the same reason, never use the command line to delete files from the GAC.


If you update an assembly and rebuild it with a new version number, you can drag and drop it into the GAC again. The old version and the new version will coexist. (They'll have the same strong name, identified by the public key token.) Also, applications that were linked to use the original version of the assembly will continue to run using that version. Figure 2-11 below shows two versions of the CakeUtils assembly in the GAC.

Figure 2-11. Two versions of CakeUtils in the GAC

Microsoft also supplies a command-line tool called Gacutil that you can use to query and maintain the GAC. To install an assembly to the GAC, you use Gacutil /i . To uninstall an assembly, you use Gacutil /u . To list the contents of the GAC, use Gacutil /l .

Configuring an Application

You can configure an application using an optional application configuration file. An application configuration file can contain information about the locations and versions of assemblies that the application references. The application configuration file has the same name as the application, with .config tacked onto the end. Assembly DLLs can also have configuration files, which can be useful if they reference other assemblies. The configuration file resides in the same folder as the application executable. Application configuration information is specified using an XML schema. The following example below shows SizeCake.exe.config, a configuration file for the SizeCake application, specifying that the SizeCake application requires the CakeUtils assembly to run:

 <?xmlversion="1.0"?> <configuration> <runtime> <assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentityname="CakeUtils"  publicKeyToken="7ec711a8af3d15be" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> 

The <assemblyBinding> tag specifies the assemblies that the application uses.

Binding Policies

If a new version of an assembly is released, you can arrange for the application to use it without relinking it, using a bindingRedirect tag:

 <?xmlversion="1.0"?> <configuration> <runtime> <assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentityname="CakeUtils"  publicKeyToken="6bf6f09321e683a7" /> <bindingRedirectoldVersion="2.0.0.0-2.0.65535.65535"  newVersion="3.0.892.24407" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> 

The example configuration causes all requests for any version of the CakeUtils assembly between versions 2.0.0.0 and 2.0.65535.65535 to be redirected to use version 3.0.892.24407 instead. (The newer version was shown earlier in Figure 2-11.)

Note

Binding policies apply only to assemblies that have strong names.


If the creator of an assembly wants to ensure that all applications that use the assembly are directed to a specific version of that assembly, she can create a publisher policy file and deploy it in the GAC with the assembly. The publisher policy file also uses an XML schema and contains a bindingRedirect tag similar to that used by the application configuration file. Publisher policy files are sometimes referred to as Quick Fix Engineering (QFE) files because they're designed to be deployed as a "quick fix" to redirect requests from a faulty assembly to a "fixed" one!

By default, the publisher policy file overrides any binding redirects performed by an application configuration file, but you can have an application ignore the publisher policy file settings by using the safe mode and specifying the <publisherPolicy apply="no" /> tag, as shown here:

 <?xmlversion="1.0"?> <configuration> <runtime> <assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentityname="CakeUtils"  publicKeyToken="6bf6f09321e683a7" /> <bindingRedirectoldVersion="2.0.0.0-2.0.65535.65535"  newVersion="3.0.892.24407" /> <publisherPolicyapply="no" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> 
Downloading Assemblies

An application configuration file (and the publisher policy file) can contain a <codeBase> element as part of the <dependentAssembly> entry. Using a code base lets you specify a location other than the GAC from where the assembly should be retrieved. It can be a folder on your computer or a URL on some Web server. Different versions of the same assembly are accessed from different code bases. If the code base specifies a URL, the assembly will be retrieved over the Internet (or your local intranet) and held in the download cache. If a subsequent request is made to the same assembly, the runtime will check the download cache first. If the required version of the assembly has already been retrieved from the same code base, it will be used rather than being fetched again.

Downloading assemblies from the Internet and running them poses some interesting security issues, which we'll address later in the chapter. The following example downloads the CakeUtils assembly from a URL on my local intranet:

 <?xmlversion="1.0"?> <configuration> <runtime> <assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentityname="CakeUtils"  publicKeyToken="6bf6f09321e683a7" /> <publisherPolicyapply="no" /> <bindingRedirectoldVersion="2.0.0.0-2.0.65535.65535"  newVersion="3.0.892.24407" /> <codeBaseversion="3.0.892.24407"  href="http://cheshirecat/CakeAssemblies/CakeUtils.dll" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> 
Search Paths

If the configuration file for an application does not specify a code base and the assembly cannot be found in the GAC, the runtime will assume that the assembly is private and will look for it in the folder containing the application, and then in any subfolder named after the assembly (and in sub-subfolders as well ”for example, you could place the CakeUtils.dll assembly in a folder named CakeUtils\CakeUtils under the application folder). If the assembly uses culture information, the search path will also include subfolders named after the required culture. Finally, you can specify a manual search path , called a binpath , that will also be searched for folders, subfolders, and culture folders that might contain the assembly. This whole process is known as probing .

The following file contains a binpath (the <probing privatePath> element) comprising \bin\retail and \assemblies. Notice the use of a semicolon to specify multiple paths. You should also be aware that these paths are relative to the application folder, not absolute path names.

 <?xmlversion="1.0"?> <configuration> <runtime> <assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentityname="CakeUtils"  publicKeyToken="6bf6f09321e683a7" /> <publisherPolicyapply="no" /> <bindingRedirectoldVersion="2.0.0.0-2.0.65535.65535"  newVersion="3.0.892.24407" /> </dependentAssembly> <publisherPolicyapply="no" /> <probingprivatePath="\bin\retail;\assemblies" /> </assemblyBinding> </runtime> </configuration> 

If the probe fails, the runtime will ask the Windows Installer to provide the assembly. The Windows Installer allows assemblies to be packaged as install-on-demand Windows Installer (MSI) files. If the Windows Installer cannot install the assembly (most likely because there is no such install package), the common language runtime will give up and generate an exception .

To summarize, here is the search algorithm the common language runtime uses to locate an assembly:

  1. Determine the version of the assembly using the metadata in the application's EXE file.

  2. Examine the application configuration file (if it exists) for a binding policy to determine whether a different version of the assembly should be used.

  3. Look in the GAC for the assembly. If a publisher policy file is present, use it to redirect to an appropriate version of the assembly (unless the application configuration file specifies that the application is using safe mode).

  4. If the assembly is not found in the GAC, check to see whether the application configuration file specifies a code base for the assembly.

  5. If no code base is specified, probe for the assembly in the application's folder based on the name of the assembly and using any binpath specified.

  6. If the assembly is still not found, ask Windows Installer to install it. If this fails, raise an exception .

.NET Configuration Tool

You can edit application configuration files and publisher policy files manually, but the .NET Framework SDK has a Microsoft Management Console snap-in called .NET Framework Configuration (shown in Figure 2-12) that makes life a little easier. Under Windows XP, you can find this in Control Panel, under Performance and Maintenance and then under Administrative Tools. (Alternatively, you can find the snap-in, called Mscorcfg.msc, under \WINDOWS\Microsoft.NET\Framework\< version >).

Figure 2-12. The .NET Framework Configuration tool

The Applications folder lets you configure an application. Simply click Add an Application to Configure, and a wizard will appear, allowing to you to browse to the application. The wizard will list the dependent assemblies and let you specify any binding policy and code base entries you require. The result will be a configuration file, created in the same folder as the application. You can also use the Applications folder to maintain existing application configuration files.

The Configured Assemblies folder allows you to create and maintain publisher policy files. Again, you can specify binding policy and code base entries for an assembly.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net