.NET Fusion
Home About Workshops Articles Writing Talks Books Contact

10. Fusion API

In the earlier pages you've seen that you use the gacutil application to add assemblies to the GAC. However, there are cases when you want to do this in code. In this part of the tutorial you'll learn about the Fusion API and how to access it. Note that in .NET version 3.0/2.0, Microsoft have provided a new header file, fusion.h, and it provides documentation. I will cover .NET version 3.0/2.0 in a separate section.

10.1 Getting Access To The Fusion API

The gacutil tool is an unmanaged tool. Clearly, it must be able to manipulate the GAC, so therefore this means that there must be some API to access the GAC, and there must be some unmanaged access to this API. To investigate this the first thing to try is to look at the unmanaged DLLs imported by this tool. If you have Visual Studio.NET installed, open the Visual Studio.NET command prompt (this is just cmd.exe with environment variables set for the Visual Studio tools). Type the following at the command line:

dumpbin %systemroot%\Microsoft.NET\Framework\v1.1.4322\gacutil.exe /imports |more

Take a look at the names of the DLLs that are imported. The only .NET DLL that is imported is mscoree.dll and there is only one imported function, LoadLibraryShim and this function is not associated with Fusion. This means that gacutil must either dynamically load a DLL, or access the GAC through a COM object. It is not immediately obvious which mechanism is used. However, the clue may be in the only method that is imported, LoadLibraryShim. This method will load a framework DLL for the latest version of the framework installed on the machine, in effect, the DLL is loaded dynamically.

If you look through the framework folder you'll immediately notice that the prime target DLL is fusion.dll. Unfortunately, there is no type library for this library, neither is there an IDL or header file for it. So we cannot determine if there is a COM object that accesses the GAC.

It is worth looking at the functions exported from fusion.dll to see if there is anything useful. Type the following to get the list of exports:

dumpbin %systemroot%\Microsoft.NET\Framework\v1.1.4322\fusion.dll /exports |more

Other than the standard COM exports, this library also exports the following:

CopyPDBs
CreateApplicationContext
CreateAssemblyCache
CreateAssemblyEnum

CreateAssemblyNameObject
CreateHistoryReader
CreateInstallReferenceEnum
GetCachePath
GetHistoryFileDirectory
InstallCustomAssembly
InstallCustomModule
LookupHistoryAssembly
NukeDownloadedCache
PreBindAssembly
PreBindAssemblyEx
ReleaseURTInterfaces

(I will explain the bold entries in a moment.) Clearly, several of these are candidates for accessing the GAC. If you search the SDK folder in the Visual Studio.NET folder for CreateAssemblyCache you'll find that there is an example called ComReg that accesses this method through platform invoke. That project defines the following:

[DllImport("Fusion.dll", CharSet=CharSet.Auto)]
internal static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, uint dwReserved);

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
 Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae")]
internal interface IAssemblyCache
{
   [PreserveSig()]
   int UninstallAssembly(
      uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName,
      IntPtr pvReserved, out uint pulDisposition);
   [PreserveSig()]
   int QueryAssemblyInfo(
      uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, IntPtr pAsmInfo);
   [PreserveSig()]
   int CreateAssemblyCacheItem(
      uint dwFlags, IntPtr pvReserved, out /*IAssemblyCacheItem*/IntPtr ppAsmItem,
      [MarshalAs(UnmanagedType.LPWStr)] String pszAssemblyName);
   [PreserveSig()]
   int CreateAssemblyScavenger(out object ppAsmScavenger);
   [PreserveSig()]
   int InstallAssembly(
      uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszManifestFilePath, IntPtr pvReserved);
}// IAssemblyCache

MSDN for .NET 1.1 does not give documentation for CreateAssemblyCache or IAssemblyCache, however, there is an article on their website and this includes an IDL file that defines functions exported from fusion.dll (the functions in bold in the list given above) and the interfaces implemented by objects in the DLL accessed through those exported functions. The IDL can be downloaded from here.

Using fusion.idl and the ComReg example you can get a clear idea of how to access the GAC. You are more likely to want to access the GAC through .NET code than unmanaged C++. These resources give you the information that you need to use Platform Invoke (as ComReg does) to access the native Fusion API, however, it would be more convenient to have a managed library that does this for you. Well, the good news is that there is such a library, it is called mscorcfg.dll. The bad news is that the class that accesses the GAC is marked as internal. However, this should not stop you from accessing the class through reflection.

The mscorcfg assembly is used by the ConfigWizards.exe assembly that is the process behind the Microsoft .NET Framework 1.1 Wizards shortcut in the Administrative Tools control panel folder. This assembly is also used by the MMC snapin Microsoft .NET Framework 1.1 Configuration tool that you have used on other pages in this tutorial.

To see this run the Depends utility that is provided as part of Visual Studio.NET. This tool can be found in the Common7\Tools\bin folder under the Visual Studio.NET folder. Run this tool, then from the File menu select Open. Browse to %systemroot%\system32 (%systemroot% is often C:\Windows) and select mmc.exe. This will load the process file but it will not run it. MMC uses .msc files to obtain the information about the snapins that it will load. The .msc file is loaded by MMC at runtime, and it will dynamically load (through COM) additional DLLs. Therefore to see what DLLs the .NET configuration snapin loads you have to run MMC under Depends. To do this select Start Profiling from the Profile menu and click OK. When MMC has started select Open from the File menu of MMC and browse for the mscorcfg.msc file in the framework directory under %systemroot%.

After the .msc file has loaded switch back to Depends and take a look at the pane that lists the DLLs used by the process. There you'll find that mmc.exe loads ole32.dll which dynamically loads mscormmc.dll. This unmanaged library hosts the .NET runtime, you can confirm this by running dumpbin /imports on this DLL and you'll see that it imports the following:

mscoree.dll
   40132C Import Address Table
   415388 Import Name Table
        0 time date stamp
        0 Index of first forwarder reference

        F CorBindToRuntimeHost
       1D GetCORVersion

The first function, CorBindToRuntimeHost, is used to host the runtime in an unmanaged process.

Back in Depends you can see that mscormmc.dll loads mscoree.dll which is a shim DLL that dynamically loads the .NET runtime DLL mscorwks.dll. The runtime DLL loads fusion.dll and it dynamically loads mscorcfg.dll using LoadLibraryEx as a data file, that is, it does not treat it as a PE library. This is acceptable because mscorcfg.dll is a .NET library. Because mscorcfg.dll is loaded as a data file you'll not get any more information in Depends, so you may as well stop MMC and close down Depends.

This has been an interesting exercise, but it has not given you much more information, other than to confirm that the MMC configuration tool uses mscorcfg.dll and fusion.dll.

10.2 Listing The Managed Applications That Have Run

The configuration tool allows you to list the assemblies in the GAC and to add a new assembly to the GAC (it also allows you to edit machine.config to specify version redirection and code base for a library). Clearly, the mscorcfg assembly has methods to access the GAC. To see what these are use ILDASM to view the assembly. You'll find that there is a namespace called Microsoft.CLRAdmin and in this will be a private class called Fusion. This uses Platform Invoke to import methods from fusion.dll and wraps the imported methods into more accessible, static, methods, as shown here:

internal static int AddAssemblytoGac(string sAssemFilename);
internal static void DiscoverFusionApps();
internal static string GetCacheTypeString(uint nFlag);
internal static StringCollection GetKnownFusionApps();
internal static bool isManaged(string sFilename);
private static void ReadCache(ArrayList alAssems, uint nFlag)
internal static ArrayList ReadFusionCache();
internal static ArrayList ReadFusionCacheJustGAC();
internal static bool RemoveAssemblyFromGac(AssemInfo aInfo);

AddAssemblyToGac and RemoveAssemblyFromGac do just what their names say.

The DiscoverFusionApps and GetKnownFusionApps are used in tandem. The former collates a list of the managed applications that have run on this machine for this user. This is stored in a StringCollection which is obtained by calling GetKnownFusionApps. The DiscoverFusionApps method is not instantaneous so instead it is called on a separate thread when the static constructor is called. A static constructor is called the first time that a class is used. If you call another static method on Fusion then this other thread will collate the application list. If you call GetKnownFusionApps within a few minutes then the list of applications will be up to date. If you call it hours later you would be advised to call DiscoverFusionApps to make sure that the list is up to date. When you call GetKnownFusionApps it will first call Join on the separate thread to make sure that it has completed its work before it will give access to the list of applications.

Recall from Example 5.2 that when you run an application Fusion maintains information about the location of the application and the versions of the libraries that it used. This is stored in an ini file stored in a subfolder of the user's Local Settings folder. DiscoverFusionApps accesses every file in this folder, opens the file and then reads the path of the application.

To test this out you must use reflection to call the method. This is very simple to do. Create a file called listFus.cs and add the following code:

using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Runtime.InteropServices;

class App
{
   static void Main()
   {
      string file = RuntimeEnvironment.GetRuntimeDirectory()
         + "mscorcfg.dll";
      Assembly assem = Assembly.LoadFile(file);
      Type type = assem.GetType("Microsoft.CLRAdmin.Fusion");
      MethodInfo mi = type.GetMethod("GetKnownFusionApps",
         BindingFlags.NonPublic|BindingFlags.Static);
      StringCollection apps = (StringCollection)mi.Invoke(null, null);
      foreach(string app in apps)
      {
         Console.WriteLine(app);
      }
   }
}

First, I obtain the path to the framework folder and use this to get the full path to the mscorcfg assembly. Then the assembly is queried for the Fusion class and then for the GetKnownFusionApps method. Finally, this method is called (with null for the first parameter - the object - because it is a static method) to obtain the collection of paths.

10.3 Listing Files In The Assembly Cache

There are three methods that can be used to access the assembly cache: ReadFusionCache, ReadFusionCacheJustGAC and ReadCache. The first two call the third one, so let's just concentrate on that. ReadCache is passed an ArrayList that it will fill with information about each file it finds in the cache. The second parameter determines which cache it will search. ReadCache calls CreateAssemblyEnum and the documentation for this method (given earlier) indicates that there are three types of cache:

typedef enum
{
   ASM_CACHE_ZAP = 0x1,
   ASM_CACHE_GAC = 0x2,
   ASM_CACHE_DOWNLOAD = 0x4
} ASM_CACHE_FLAGS;

The last two should be familiar to you, they are the Global Assembly Cache and the Download Cache. The first one uses a terminology that I have not used already: the Zap Cache. However, this is not as exciting as it appears to be, the Zap Cache merely contains the assemblies that have been JITted with the ngen tool. ReadFusionCache calls ReadCache for both the GAC and the Zap Cache; ReadFusionCacheJustGAC  calls ReadCache for just the GAC. The GetCacheTypeString method mentioned above takes an integer and it returns a string with the name of the cache that the integer represents.

To test this out involves a bit more manipulative work with reflection. The problem is that ReadCache returns instances of an internal type called AssemInfo that is defined in mscorcfg. We don't have access to that type, so instead we need to create our own equivalent and initialize it with one of the objects returned from ReadCache. Here is the first part of listCache.cs:

using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;

class AssemInfo
{
   public string Name = null;
   public string Version = null;
   public string Locale = null;
   public string PublicKey = null;
   public string PublicKeyToken = null;
   public string Modified = null;
   public string Codebase = null;
   public string ProcType = null;
   public string OSType = null;
   public string OSVersion = null;
   public uint nCacheType = 0;
   public string sCustom = null;
   public string sFusionName = null;

   public AssemInfo(object o)
   {
      SetString(o, "Name");
      SetString(o, "Version");
      SetString(o, "Locale");
      SetString(o, "PublicKey");
      SetString(o, "PublicKeyToken");
      SetString(o, "Modified");
      SetString(o, "Codebase");
      SetString(o, "ProcType");
      SetString(o, "OSType");
      SetString(o, "OSVersion");
      SetString(o, "sCustom");
      SetString(o, "sFusionName");
      FieldInfo src = o.GetType().GetField("nCacheType",
         BindingFlags.NonPublic|BindingFlags.Instance);
      nCacheType = (uint)src.GetValue(o);
   }
   private void SetString(object o, string name)
   {
      FieldInfo src = o.GetType().GetField(name,
         BindingFlags.NonPublic|BindingFlags.Instance);
      FieldInfo dst = this.GetType().GetField(name);
      dst.SetValue(this, src.GetValue(o));
   }
}

The SetString method uses reflection to access the specified field on the initializer object and uses reflection to assign the same name field on the current object.

The Main function is quite straightforward, it reads the command line and uses this to determine the cache to search: zap, gac or download.

class App
{
   static void Main(string[] args)
   {
      string file = RuntimeEnvironment.GetRuntimeDirectory() + "mscorcfg.dll";
      Assembly assem = Assembly.LoadFile(file);
      Type type = assem.GetType("Microsoft.CLRAdmin.Fusion");

      uint flag = 2;
      if (args.Length > 0)
      {
         switch (args[0].ToLower())
         {
         case "zap":
            flag = 1;
            break;
         case "gac":
            flag = 2;
            break;
         case "download":
            flag = 4;
            break;
         }
      }

      MethodInfo mi = type.GetMethod("ReadCache",
         BindingFlags.NonPublic|BindingFlags.Static);
      ArrayList assems = new ArrayList();
      mi.Invoke(null, new object[]{assems, flag});
      foreach (object o in assems)
      {
         AssemInfo a = new AssemInfo(o);
         Console.WriteLine(a.sFusionName);
      }
   }
}

Compile this code. Now run it three times, to get the contents of each of the three caches.

10.4 .NET Version 3.0/2.0

The .NET 2.0 SDK (included in the .NET 3.0 SDK which is part of the Windows SDK) provides a header file, fusion.h, which is essentially obtained from the Fusion IDL file I mentioned earlier. However, it does include the prototypes for some of the C exported functions. These functions and the interfaces are now documented in MSDN. Here's a summary of the functions (implemented in fusion.dll, unless otherwise noted):

FunctionDescription
ClearDownloadCache Clears the cache of downloaded assemblies, essentially the same as gacutil -cdl
CreateAssemblyCache Creates an assembly cache object, not create the cache. This object allows you to install (gacutil -i), uninstall (gacutil -u) or gather information about a GAC assembly.
CreateAssemblyEnum Creates an object that allows you to enumerate assemblies in the GAC; either all assemblies, or assemblies that have a particular name. Equivalent to gacutil /l
CreateAssemblyNameObject Create the name of an assembly, for example, so that you can pass it to CreateAssemblyEnum
CreateInstallReference Obtain an enumerator object with all the references of a specific assembly
GetCachePath Gets the paths to the various assembly caches
GetFileVersion Returns a string with the version of the runtime that a particular assembly was built for (mscoree.dll)
GetHashFromAssemblyFile Creates a hash from an assembly (mscoree.dll)
GetHashFromFile Create the hash of a file (not an assembly) (mscoree.dll)

There are also a whole collection of methods to do with the strong name, these are all in the mscoree.dll library:

FunctionDescription
StrongNameCompareAssemblies Determines of two assemblies differ only by the strong name (sn -D)
StrongNameErrorInfo Gets the last error from calling one of the strong name functions
StrongNameFreeBuffer Releases the memory of the buffers allocated by other strong name functions
StrongNameGetBlob Returns the strong name signature for the specified assembly on disk
StrongNameGetBlobFromImage Returns the strong name signature for the specified assembly in memory
StrongNameGetPublicKey Returns a public key from a supplied public/private key pair (similar to sn -p except that with this function you have to load the key pair file in memory)
StrongNameHashSize Returns the size of a buffer required for a hash of the specific hash algorithm
StrongNameKeyDelete Remove a key from a key container (sn -d)
StrongNameKeyGen Creates a public/private key pair (sn -k)
StrongNameKeyGenEx Creates a public/private key pair, for a specific key size (sn -k)
StrongNameKeyInstall Adds a key to a key container (sn -i)
StrongNameSignatureGeneration Signs an assembly using the key in memory, can also return the strong name.
StrongNameSignatureGenerationEx Signs an assembly using the key in a container, can also return the strong name.
StrongNameSignatureSize Returns the strong name signature size for the supplied public/private key pair
StrongNameSignatureVerification Determines if an assembly on disk has a strong name
StrongNameSignatureVerificationEx Restricted version of StrongNameSignatureVerification
StrongNameSignatureVerificationFromImage Determines if an assembly in memory has a strong name
StrongNameTokenFromAssembly Returns the strong name token of the specified assembly on disk (sn -T)
StrongNameTokenFromAssemblyEx Returns the strong name token and public key of the specified assembly on disk (sn -Tp)
StrongNameTokenFromPublicKey Returns the strong name token from the public key keld in memory (similar to sn -t except that you have to load the public key in memory)

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 Eleven

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