- Modules Defined
- Assemblies Defined
- Assembly Names
- Public Keys and Assemblies
- The CLR Loader
- Resolving Names to Locations
- Versioning Hazards
- Where Are We?
Resolving Names to Locations
After the assembly resolver decides which version of the assembly to load, it must locate a suitable file to pass to the underlying assembly loader. The CLR looks first in the directory specified by the DEVPATH operating system (OS) environment variable. This environment variable is typically not set on the deployment machine. Rather, it is intended for developer use only and exists to allow delay-signed assemblies to be loaded from a shared file-system directory. Moreover, the DEVPATH environment variable is considered only if the following XML configuration file element is present in the machine.config file:
<configuration> <runtime> <developmentMode developerInstallation="true" /> </runtime> </configuration>
Because the DEVPATH environment variable is not intended for deployment, the remainder of the chapter will ignore its existence.
Figure 2.12 shows the entire process the assembly resolver goes through in order to find an appropriate assembly file. In normal deployment scenarios, the first location that the assembly resolver uses to find an assembly is the global assembly cache (GAC). The GAC is a machine-wide code cache that contains assemblies that have been installed for machine-wide use. The GAC allows administrators to install assemblies once per machine for all applications to use. To avoid system corruption, the GAC accepts only assemblies that have valid digital signatures and public keys. Additionally, entries in the GAC can be deleted only by administrators, something that prevents non-admin users from deleting or moving critical system-level components.
Figure 2.12: Assembly Resolution
To avoid ambiguity, the assembly resolver will look in the GAC only if the requested assembly name contains a public key. This prevents requests for generic names such as utilities from being satisfied by the wrong implementation. The public key can be provided either explicitly as part of an assembly reference or parameter to Assembly.Load or implicitly via the qualifyAssembly configuration file element.
The GAC is controlled by a system-level component (FUSION.DLL) that keeps a cache of DLLs under the %WINNT%\Assembly directory. FUSION.DLL manages this directory hierarchy for you and provides access to the stored files based on the four-part assembly name, as shown in Table 2.4. Although one can traverse the underlying directories, the scheme used by FUSION to store cached DLLs is an implementation detail that is guaranteed to change as the CLR evolves. Instead, you must interact with the GAC via the GACUTIL.EXE tool or some other facade over the FUSION application programming interface (API). One such facade is SHFUSION.DLL, a Windows Explorer shell extension that provides a user-friendly interface to the GAC.
Table 2.4 Global Assembly Cache
Name |
Version |
Culture |
Public Key Token |
Mangled Path |
yourcode |
1.0.1.3 |
de |
89abcde... |
t3s\e4\yourcode.dll |
yourcode |
1.0.1.3 |
en |
89abcde... |
a1x\bb\yourcode.dll |
yourcode |
1.0.1.8 |
en |
89abcde... |
vv\a0\yourcode.dll |
libzero |
1.1.0.0 |
en |
89abcde... |
ig\u\libzero.dll |
If the assembly resolver cannot find the requested assembly in the GAC, the assembly resolver then tries to use a CODEBASE hint to access the assembly. A CODEBASE hint simply maps an assembly name to a file name or URL where the module containing the assembly manifest is located. Like version policies, CODEBASE hints are located in both application- and machine-wide configuration files. Listing 2.6 shows an example configuration file that contains two CODEBASE hints. The first hint maps version 1.2.3.4 of the Acme.HealthCare assembly to the file C:\acmestuff\Acme.HealthCare.DLL. The second hint maps version 1.3.0.0 of the same assembly to the file located at http://www.acme.com/bin/Acme.HealthCare.DLL.
Assuming that a CODEBASE hint is provided, the assembly resolver can simply load the corresponding assembly file, and the loading of the assembly proceeds as if the assembly were loaded by an explicit CODEBASE a la Assembly.LoadFrom. However, if no CODEBASE hint is provided, the assembly resolver must begin a potentially expensive procedure for finding an assembly file that matches the request.
Listing 2.6: Specifying the CODEBASE Using Configuration Files
<?xml version="1.0" ?> <configuration xmlns:asm="urn:schemas-microsoft-com:asm.v1" > <runtime> <asm:assemblyBinding> <!-- one dependentAssembly per unique assembly name --> <asm:dependentAssembly> <asm:assemblyIdentity name="Acme.HealthCare" publicKeyToken="38218fe715288aac" /> <!-- one codeBase per version --> <asm:codeBase version="1.2.3.4" href="file://C:/acmestuff/Acme.HealthCare.DLL"/> <asm:codeBase version="1.3.0.0" href="http://www.acme.com/Acme.HealthCare.DLL"/> </asm:dependentAssembly> </asm:assemblyBinding> </runtime> </configuration>
If the assembly resolver cannot locate the assembly using the GAC or a CODEBASE hint, it performs a search through a series of directories relative to the root directory of the application. This search is known as probing. Probing will search only in directories that are at or below the APPBASE directory (recall that the APPBASE directory is the directory that contains the application's configuration file). For example, given the directory hierarchy shown in Figure 2.13, only directories m, common, shared, and q are eligible for probing. That stated, the assembly resolver will probe only into subdirectories that are explicitly listed in the application's configuration file. Listing 2.7 shows a sample configuration file that sets the relative search path to the directories shared and common. All subdirectories of APPBASE that are not listed in the configuration file will be pruned from the search.
Figure 2.13: APPBASE and the Relative Search Path
Listing 2.7: Setting the Relative Search Path
<?xml version="1.0" ?> <configuration xmlns:asm="urn:schemas-microsoft-com:asm.v1" > <runtime> <asm:assemblyBinding> <asm:probing privatePath="shared;common" /> </asm:assemblyBinding> </runtime> </configuration>
When probing for an assembly, the assembly resolver constructs CODEBASE URLs based on the simple name of the assembly, the relative search path just described, and the requested culture of the assembly (if present in the assembly reference). Figure 2.14 shows an example of the CODEBASE URLs that will be used to resolve an assembly reference with no culture specified. In this example, the simple name of the assembly is yourcode and the relative search path is the shared and common directories. The assembly resolver first looks for a file named yourcode.dll in the APPBASE directory. If there is no such file, the resolver then assumes that the assembly is in a directory with the same name and looks for a file with that name under the yourcode directory. If the file is still not found, this process is repeated for each of the entries in the relative search path until a file named yourcode.dll is found. If the file is found, then probing stops. Otherwise, the probe process is repeated, this time looking for the file named yourcode.exe in the same locations as before. Assuming that a file is found, the assembly resolver verifies that the file matches all properties of the assembly name specified in the assembly reference and then loads the assembly. If one of the properties of the file's assembly name does not match all of the (post-version policy) assembly reference's properties, the Assembly.Load call fails. Otherwise, the assembly is loaded and ready for use.
Figure 2.14: Culture-Neutral Probing
Probing is somewhat more complex when the assembly reference contains a culture identifier. As shown in Figure 2.15, the preceding algorithm is augmented by looking in subdirectories whose names match the requested culture. In general, applications should keep relative search paths small to avoid excessive load-time delays.
Figure 2.15: Culture-Dependent Probing