.NET Fusion
Home About Workshops Articles Writing Talks Books Contact

4. Shared Assemblies

Shared assemblies are libraries that are intended to be used by more than one application. You should think carefully before you decide that a library should be shared, because in most cases libraries should be private.

A shared library must have a strong name and should have a version (if you do not specify a version then the default value of 0.0.0.0 will be used). To share an assembly it must be installed into the Global Assembly Cache and to do this you use the gacutil tool. 

4.1 Shared Assemblies

For example, use app.cs from the last page, delete the configuration file you have been using (app.exe.config) and then change the library source code so that the library has a version of 1.0.0.0. Make sure that you have a key file key.snk and then compile the library and the process.

Now install the library into the GAC using the following:

gacutil -i lib.dll

Now delete the library in the application folder and run the process. The process will load the library and you'll get a result like this on version 1.1:

library version: 1.0.0.0
codebase: file:///C:/windows/assembly/gac/lib/1.0.0.0__3bf941bb1f722efe/lib.dll

.NET Version 3.0
The output for .NET version 3.0/2.0 looks like this:

library version: 1.0.0.0 codebase: file:///C:/WINDOWS/assembly/GAC_MSIL/lib/1.0.0.0__3bf941bb1f722efe/lib.dll

As you can see, in .NET 3.0/2.0, there is a change in the implemenation of the GAC.

Notice that the codebase is for a folder that has a name based on the version, the culture and the PublicKeyToken for the library. The format is:

Version_Culture_PublicKeyToken

Since lib does not have a culture (Culture=neutral) the folder name has a double underscore between the version and public key token. This scheme means that a different version of the assembly would be installed in a different folder. Now try to recompile the process assembly:

csc app.cs /r:lib.dll

This will fail with the following error:

error CS0006: Metadata file 'lib.dll' could not be found

The reason is that the compiler must have access to the metadata of the libraries that the assembly will use, and by default it looks for the metadata folder in the current folder or the .NET framework folder (%systemroot%\Microsoft.NET\Framework\<version> where <version> is the version of the framework used by the compiler). You could use the full path to the assembly's code base in the /r switch but it is easier to leave the assembly in the build folder so that the compiler can access the metadata from there.

You can list the assemblies in the GAC using the /l switch, however, since there are likely to be many assemblies in the GAC it is better to use a filter. For example you can use the following to list all assemblies with a short name of lib:

C:\TestFolder>gacutil -l lib

Microsoft (R) .NET Global Assembly Cache Utility. Version 1.1.4322
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

The Global Assembly Cache contains the following assemblies:
lib, Version=1.0.0.0, Culture=neutral,
   PublicKeyToken=3bf941bb1f722efe, Custom=null

The cache of ngen files contains the following entries:

Number of items = 1

.NET Version 3.0
The output for .NET version 3.0/2.0 looks like this:

lib, Version=1.0.0.0, Culture=neutral,
   PublicKeyToken=3bf941bb1f722efe, processorArchitecture=MSIL

The significant change here is that the undocumented Custom item has been replaced with processorArchitecture. This appears to be one of the ProcessorArchitecture enumeration, and in this case it indicates that the assembly contains 'neutral with respect to processor and bits-per-word'.

You can also use the Fusion namespace extension in Windows Explorer. To do this start Windows Explorer and navigate to %systemroot%\assembly (for example C:\Windows\assembly). You will see something like the following for .NET version 1.1 (the property dialog is shown using the context menu for the item).

Note that this property page gives a CodeBase for .NET version 1.1, but not for .NET version 3.0/2.0. This value specifies where the original file was located when you called gacutil and not the location of the file that will be loaded. As specified above, when you load a shared library, the library file will be loaded from a subfolder under %systemroot%\assembly\GAC. This raises the question of where the property page gets this information. Open a command window and navigate to %systemroot%\assembly\GAC\lib (%systemroot% is usually C:\windows). In this folder you will find a subfolder with a name like 1.0.0.0__ede6789fb0b13219, move to this folder and type dir to get a list of files. Here, you'll find a copy of lib.dll and a file called __AssemblyInfo__.ini. Type this latter file to the console and you'll find something like this:

[AssemblyInfo]
Signature=42d864e64d3552228f2b7af7db4257d572067c34
MVID=1d05a1fc660dec45b0ce70b3a49b479c
URL=file:///C:/TestFolder/lib.dll
DisplayName=lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ede6789fb0b13219

This file contains the path that we are looking for. .NET 3.0/2.0 does not have this file.

The Microsoft .NET Framework Configuration tool (mscorcfg.msc) MMC snapin under the Administrative Settings control panel folder also allows you to view the the GAC. There are two possibilities: Assembly Cache and Configured Assemblies:

.NET Version 3.0
Note that for .NET version 3.0/2.0 there is an extra layer in the hierarchy, My Computer is beneath the .NET Framework 2.0 Configuration node. (Note that this tool is used for both version 2.0 and 3.0)

The Assembly Cache lists the assemblies installed in the GAC and it also allows you to add an assembly to the GAC. (If this is the first time that you have used this option, you'll find that you will have to click on the View List of Assemblies in the Assembly Cache link)

The Configured Assemblies node will list assemblies in the GAC that have binding or redirect settings in machine.config. Since machine.config affects all loading assemblies you should treat this node as providing global rebinding information. Rebinding information means that you can specify a codeBase, I find this a bit odd. If you put an assembly in the GAC then you specify that you want it to be shared by all applications and you want the system to maintain versioning of that DLL through publisher policy files. If you specify a codeBase for a shared library you indicate that different versions will be found outside of the GAC. These versions are still shared, in the sense that many different applications can load them, but they do not use the facilities of the GAC. In general, it is not a good idea to use this facility.

4.2 Shared and Private Assemblies

Now change the library so that the version is 1.1.0.0, compile the library and the process, install the library into the GAC but do not delete it from the application folder. Use gacutil -l lib or the Windows Explorer namespace extension to show that two versions of the library are installed in the GAC. Run the process to confirm that the new version of the library is picked up from the GAC - even though there is a private copy of the assembly, Fusion prefers the version in the GAC.

There are two ways to remove an assembly from the GAC. The simplest way is to use the Windows Explorer namespace extension. For example, use the namespace extension to select the library that you have just added to the GAC (version 1.1.0.0) right click, select Delete (Uninstall for .NET version 3.0/2.0) and then click on Yes on the dialog to confirm the deletion. The second way is to use gacutil on the command line with the /u switch. It is possible to use this switch with a partial name (like lib) but this will mean that all versions of the library will be removed from the GAC, including libraries with a similar short name to yours but provided by another publisher (that is, with a different public key token). The safest way to use gacutil -u is to provide the full name of the library. The easiest way to do this is to call gacutil -l first to get the full name, copy the name to the clipboard and then type gacutil -u and paste the name within quotes:

C:\TestFolder>gacutil -u "lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ede6789fb0b13219"

4.3 GAC Folder Security

Shared assemblies are stored in the GAC. Earlier on this page you saw that the GAC is located under %windir% in a folder called assembly. The view in Windows Explorer is through a namespace extension and so it hides the real folder structure. This means that you cannot get the folder's real properties using explorer. However, it is possible to temporarily disable the namespace extension. To do this use the command line to move to the GAC folder:

cd %windir%\assembly

Now you need to rename the file controlling the namespace extension so that explorer will provide the folder structure rather than the namespace extension:

attrib desktop.ini -s -h -r
rename desktop.ini desktop.ini.old

Now run explorer again so that the new settings are used. You'll see something like this:

Now view the properties of the assembly folder (press Alt-Enter) and look on the Security tab. If you click on the Advanced button you will get a view of the access of all accounts. On my machine I get the following:

Alias Access Applies to
Administrators Full Control This folder; subfolders and files
CREATOR OWNER Full Control Subfolders and files
Power Users Modify This folder; subfolders and files
SYSTEM Full Control This folder; subfolders and files
Users Read & Execute This folder; subfolders and files

What this tells us is that ordinary users cannot add or modify files in the GAC, this action is restricted to Administrators and Power Users. Now shutdown explorer and rename the desktop.ini file so that the namespace extension is viewed in explorer:

rename desktop.ini.old desktop.ini
attrib desktop.ini +s +h +r

The consequence of these security settings is that the system is confident that the files in the GAC are secure, and that no one untrusted can alter them. Thus, the only time that the framework must check a shared assembly is when it is first installed into the GAC. When you install an assembly the runtime checks to see that the assembly has a strong name. This check is not performed when a shared assembly is loaded and this makes the load operation slightly quicker.

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Five

This page is (c) 2007 Richard Grimes, all rights reserved