The following are the topics available in this page.
Private Assemblies
As the name suggests, library assemblies are intended to be used by other assemblies. Libraries can be shared or private. A shared assembly can be used by more than one application. A private assembly is used only by the application in its folder (or immediate parent folder. This arrangement means that if multiple applications use the same private library, there will be multiple copies of that library on your hard disk, but it does mean that the applications will have the library that they were built to use. A private assembly can have any name that you choose, and it does not need a strong name. The private assembly is located in the same folder as the application that uses it, or in a subfolder. This subfolder has either the same name as the short name of the assembly, or it has the name of the culture of the assembly, or it is a subfolder mentioned in the privatePath attribute of the <probing> section of the application configuration file. So, if you build an assembly named utils.dll with a culture of “en-GB,” the assembly’s PE file can be in the application folder (AppDomain::BaseDirectory), in a folder named utils, or in a folder named en-GB.
Shared Assemblies
Shared assemblies are stored in a special folder on your hard disk named the global assembly cache (GAC). Figure 18 shows the GAC on my machine. In general, the GAC appears as a folder named assembly under the %SYSTEMROOT% folder of your machine. This folder is actually a namespace extension provided by shfusion.dll. You are not expected to view the actual folder structure, but if you are interested, you can navigate the GAC through the command line. The namespace extension actually gives a list of assemblies in the GAC and assemblies in the native image cache. (The Type column lists “native images” for these assemblies in the native image cache.) Assemblies can be in either or both of these locations. The native image cache contains assemblies that have been PreJITted.
Figure 17 |
Figure 18: The global assembly cache
The GAC contains only shared assemblies, and any installer application run under the Administrators account can install an assembly in the GAC. If the GAC was simply a FAT32 or an NTFS folder, this installation could cause a potential problem because the name of the PE file is not sufficiently unique to prevent an installer copying over an existing library. The GAC is actually a series of folders. Figure 20 shows the format of the GAC when we disable the Fusion namespace extension. To do this, use the command line to navigate to %systemroot%\assembly, then use attrib to remove the SHR attributes on Desktop.ini, and then rename this file to something else. Remember to rename the file and apply the SHR attributes after you have finished examining the folder structure. The assembly folder has a folder named GAC, and immediately below the GAC folder is a folder that has the short name of each assembly in the GAC. Within each of these subfolders are folders that are named according to the version, culture, and public key token of the assembly. The most important of these is the public key token: an assembly must have a strong name if it is to be put in the GAC.
There are several ways to configure assemblies in the GAC. You can use the gacutil utility to add and remove assemblies; you can use the -i switch and the assembly filename to add an assembly and the /u switch with the full name of the assembly to remove the assembly from the GAC.
Figure 19
The Microsoft Installer can also add assemblies to the GAC, and you can use the namespace extension. (You can drag and drop an assembly to install it, or use the delete context menu to remove an assembly). The assembly folder in the namespace extension also shows a subfolder named Download. This folder contains assemblies that have been downloaded from other machines.
Figure 20: The global assembly cache shown with the namespace extension disabled
PreJITted Assemblies
The shell extension (Figure 18) gives the short name, the version, the culture, and the public key token of the assemblies that are installed. There is also a column named Type. This column indicates whether the assembly is MSIL or has been PreJITted. In Figure 18, you can see that the mscorlib assembly has been PreJITted because the Type column has the phrase Native Images. A PreJITted assembly is one where the entire assembly has been run through the JITter so that all the MSIL has been compiled to native code and then saved to a special area of your hard disk called the Native Image Cache. Figure 22 shows the Native Image Cache in Windows Explorer with the Fusion namespace disabled. You can PreJit your own assemblies with the tool ngen.
------------------------------------------------------
Figure 21
When you run this tool, it will JIT-compile the assembly and then install the JITted assembly in the native image cache. The native image cache can hold more than one version of the PreJITted assembly, but unlike the GAC, you do not need to provide a strong name for the assembly.
Figure 22: The Native Image Cache shown with the namespace extension disabled
PreJITted assemblies do not contain the metadata tables held by MSIL assemblies: ILDASM will show only the manifest for a PreJITted assembly. However, this limitation is not a problem because you use the non-PreJITted assembly in #using statements, and at run time, the .NET Framework will locate the relevant PreJITted assembly in the native image cache, or if the Framework cannot find the right library, it will resort to JITting the non-PreJITted assembly. This strategy means that a machine that has a PreJITted assembly will also have the non-PreJITted assembly, so you cannot use this scheme to hide the implementation of your code. Figure 18 shows that mscorlib is PreJITted, but the assembly that you refer to in #using statements is the assembly in the .NET Framework system folder. The reason for PreJITting is to make the initial loading of an assembly faster. However, once loaded into memory, a PreJITted assembly is unlikely to have any performance gain over code in a non-PreJITted assembly.
Locating Assemblies
Assemblies are loaded in two ways, dynamic or static. A dynamically loaded assembly is obtained through a call to Assembly::Load, and although this mechanism gives great flexibility, it also requires more work than static loading because you have to use the Activator class to create objects rather than the managed new. Assembly::Load requires that you provide the full name of the assembly. You can also provide partial information about the assembly using Assembly::LoadWithPartialName; however, this strategy can make the load process slower, and you do not get the benefit of versioning. When you use the #using statement, you indicate that you want to statically link to the assembly. The compiler will add the complete name of the assembly to the manifest of the referring assembly, which includes the short name, the version, the culture, and if it has a strong name, the public key token. Whether an assembly is statically or dynamically loaded, it is subject to Fusion probing.
Probing is the name for the mechanism that Fusion uses to locate an assembly. The first thing that Fusion does is check to see whether the assembly has a strong name. If the assembly has a strong name, .NET versioning can be used, so Fusion determines the version of the assembly. This information will be specified in the manifest of the referring assembly (or the AssemblyName if the assembly is dynamically loaded), but it can be changed through the application’s configuration file. The relevant section is <assemblyBinding> within the <runtime> section, as shown in the following code:
<configuration>
<runtime>
<assemblyBinding>
<dependentAssembly>
<assemblyIdentity name="myAssem"
PublicKeyToken="c4c3b887dbc8b8c1"/>
<bindingRedirect oldVersion="1.1.0.0"
newVersion="1.2.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
For each assembly that you want to provide binding information, you have to have a <dependentAssembly> element. The name of the assembly is given in the <assemblyIdentity> element. This name does not have a version because that information is given by the <bindingRedirect> tag. This entry indicates that version 1.2.0.0 is installed on the machine, but when an application that requires version 1.1.0.0 of the assembly runs, the runtime will load the newer version instead. If the assembly is shared, there might be a publisher policy file that can override both the version given in the referring assembly’s manifest or in the application file.
Fusion now has the complete name of the assembly and can test to see whether the assembly has already been loaded. If the assembly is loaded, the loaded version will be used. Otherwise, Fusion starts the process of locating the assembly. If the assembly has a strong name, it could be a shared assembly, so the next step performed by Fusion is to check the GAC. If the assembly is not in the GAC, Fusion treats the assembly as a private assembly and attempts to determine the private assembly’s location. The first check Fusion makes is the codebase. A dynamic loaded assembly can specify this location through the AssemblyName::Codebase property or through Assembly::LoadFrom. Also, a Codebase can be added to the registry entry for an assembly called through COM interop with the /codebase switch of RegAsm.
A static-bound assembly can be loaded from a location other than the default location (another folder or a URL) through the <codeBase> element in the application configuration file (or a policy file). When you specify a codebase folder, it must be a subfolder of the application folder. You cannot specify a global folder. This reinforces the fact that the only way that you can share an assembly is to put the assembly in the GAC. Once the codebase has been checked, Fusion will check the current folder (AppDomain::BaseDirectory), a subfolder with the short name of the library assembly, or a subfolder with the name of the library assembly’s culture. If the library assembly cannot be located in these folders, Fusion will check to see whether there is a <probing> element in the application configuration file. This element can have an attribute named privatePath that is a list of subfolders, and Fusion will check these folders (and if the library has a culture, a folder with the culture name) for the assembly.
If all these checks fail, Fusion cannot find the assembly and it will throw an exception. You can then use the Fusion Log Viewer to look at all the tests performed and why they failed. When you are developing a shared assembly, you are likely to build the assembly frequently. To test such an assembly, you’ll have to install the assembly in the GAC each time you rebuild it. This process can be tedious, so the Framework offers a solution with the DEVPATH environment variable. You put the path to the directory that contains the assembly in DEVPATH and then add the <developmentMode> to machine.config.
<!-- machine.config -->
<configuration>
<runtime>
<developmentMode developerInstallation="true"/>
</runtime>
</configuration>