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:
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:
Other than the standard COM exports, this library also exports the following:
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:
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:
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 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 |
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:
{
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.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.
{
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):
| Function | Description |
|---|---|
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:
| Function | Description |
|---|---|
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.