Um die vorhandenen Assemblies im GAC abzufragen gibt es viele Möglichkeiten. Eine, basierend auf dem Abfragen der Registrierung, habe ich in diesem Beitrag auf .NET Snippets.de gefunden. Die mit Sicherheit am häufigsten verwendete Methode dürfte jedoch die Abfrage mit gacutil.exe aus dem SDK sein. Falls jetzt noch die Information benötigt wird, ob ein natives Image einer Assembly vorhanden ist, wird in der Regel der Native Image Compiler ngen.exe verwendet, da gacutil.exe diese Möglichkeit nicht bietet. Unter Verwendung der Fusion-API ist dieses Problem jedoch relativ einfach zu lösen. Hier zum Vergleich die Ausgabe von gacutil.exe und einer Anwendung welche die Fusion-API verwendet. Es wird die Assembly System abgefragt.

gacutil query

Das Tool gacutil.exe gibt richtig die beiden, im GAC installierten, Versionen 2.0.0.0 und 1.0.5000.0 aus. Ob jedoch eines dieser Assemblies als natives Image vorliegt ist so nicht zu erfahren.

 

 

GAC AdminDas Tool GacAdminCmd.exe gibt wie gacutil.exe die beiden Versionen im GAC aus und zusätzlich die gefundene Version 2.0.0.0 aus dem ZAP (Native Images Cache). Die Assembly im ZAP wurde ohne Zuhilfenahme der ngen.exe ermittelt. Um zu erfahren wie, ist eine nähere Betrachtung der statischen Methode CreateAssemblyEnum der Fusion-API notwendig. Der vierte Parameter erwartet ein DWORD, welches ein Flag aus der ASM_CACHE_FLAGS-Enumeration darstellt. Wenn der Wert ASM_CACHE_ZAP, aus der Aufzählung, an den Parameter dwFlags der CreateAssemblyEnum-Methode übergeben wird, sucht die Methode im Native Images Cache nach der Assembly. Wie bereits eingangs erwähnt: relativ einfach.

Die Methode, welche die Assemblies ermittelt und auf den Bildschirm bringt, ist sehr einfach gehalten wie man hier sehen kann.

private static void listAssemblies(string assemblyName, bool listZap)
{
    int i = 0;
    string asm = null;
    Console.WriteLine();
    // Suche nach den Assemblies im GAC
    // wenn assemblyName ein NULL-Verweis ist, werden alle Assemblies
    // im GAC aufgelistet
    AssemblyCacheEnum acEnum = new AssemblyCacheEnum(
									assemblyName,
									AssemblyCacheLocation.Gac);
	while ((asm = acEnum.NextAssembly) != null)
    {
        Console.WriteLine("  " + asm);
        i++;
    }
    Console.WriteLine();
    Console.WriteLine(string.Format(
		CultureInfo.CurrentCulture,
        Resources.AsmCountInGacMsg,
		i));
    // Suche nach den Assemblies im ZAP
    if (listZap)
    {
        Console.WriteLine();
        Console.WriteLine();
        i = 0;
        // wenn assemblyName ein NULL-Verweis ist, werden alle Assemblies
        // im ZAP aufgelistet
        acEnum = new AssemblyCacheEnum(
			assemblyName,
			AssemblyCacheLocation.Zap);
		while ((asm = acEnum.NextAssembly) != null)
        {
            Console.WriteLine("  " + asm);
            i++;
        }
        Console.WriteLine();
        Console.WriteLine(string.Format(
			CultureInfo.CurrentCulture,
            Resources.AsmCountInZapMsg,
			i));
    }
}

Zunächst wird ein Instanz der AssemblyCacheEnum-Klasse erzeugt und der Name der Assembly sowie der Ort an dem gesucht werden soll im Konstruktor  übergeben. In einer Schleife werden dann die zurückgelieferten Namen auf dem Bildschirm ausgegeben sowie die Anzahl der gefundenen Assemblies mittels der Laufvariablen i ermittelt und ebenfalls ausgegeben.

Da die Implementierung der Fusion-API sehr umfangreich ist, beschränke ich mit hier auf die relevanten Code-Passagen.

Zunächst die bereits erwähnte statische Methode CreateAssemblyEnum

[DllImport("fusion.dll")]
internal static extern int CreateAssemblyEnum(
        out IAssemblyEnum ppEnum,
        IntPtr pUnkReserved,
        IAssemblyName pName,
        AssemblyCacheLocation flags,
        IntPtr pvReserved);

sowie die ebenfalls benötigte Methode CreateAssemblyNameObject. Sie wird benötigt um einen Zeiger auf ein IAssemblyName-Objekt zu erhalten. Hier muss darauf geachtet werden, dass der Parameter szAssemblyName, also der Name der gesuchten Assembly, in eine Unicode-Zeichenfolge gemarshallt wird.

[DllImport("fusion.dll")]
internal static extern int CreateAssemblyNameObject(
        out IAssemblyName ppAssemblyNameObj,
        [MarshalAs(UnmanagedType.LPWStr)]
        string szAssemblyName,
        CreateAssemblyNameObjectFlags flags,
        IntPtr pvReserved);

Zu den Schnittstellen IAssemblyName und IAssemblyEnum ist nicht weiter viel zu sagen. Der Vollständigkeit halber hier die relevanten Passagen.

IAssemblyName:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("CD193BC0-B4BC-11d2-9833-00C04FC31D2E")]
internal interface IAssemblyName
{
    [PreserveSig()]
    int GetDisplayName(
    StringBuilder pDisplayName,
    ref int pccDisplayName,
    int displayFlags);
}

IAssemblyEnum:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")]
internal interface IAssemblyEnum
{
    [PreserveSig()]
    int GetNextAssembly(
        IntPtr pvReserved,
        out IAssemblyName ppName,
        int dwFlags);
}

Da die aus dem ersten Listing, in der Methode listAssemblies verwendete, Klasse AssemblyCacheEnum sehr umfangreich ist zeige ich hier nur zwei Klassen-Member, den Konstruktor und zwei benötigte private Methode.

public sealed class AssemblyCacheEnum : IDisposable
{
    #region Class Members
    private IAssemblyEnum assemblyEnum = null;
    private IAssemblyName fusionName = null;
    #endregion
    public AssemblyCacheEnum(
			string assemblyName,
			AssemblyCacheLocation location)
    {
        // HRESULT erzeugen
        int hr = 0;
        if (assemblyName != null)
        {
            hr = NativeMethods.CreateAssemblyNameObject(
                out fusionName,
                assemblyName,
                CreateAssemblyNameObjectFlags.ParseDisplayName,
                IntPtr.Zero);
        }
        if (hr >= 0)
        {
            hr = NativeMethods.CreateAssemblyEnum(
                out assemblyEnum,
                IntPtr.Zero,
                fusionName,
                location,
                IntPtr.Zero);
        }
        if (hr < 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
    }
    [SecurityPermission(
	 SecurityAction.LinkDemand,
	 UnmanagedCode = true)]
    private string getNextAssembly()
    {
        int hr = 0;
        // eine Instanz der IAssemblyName-Schnittstelle erzeugen.
        fusionName = null;
        // prüfen ob das Ende der Aufzählung bereits erreicht wurde
        if (done)
        {
            return null;
        }
        // jetzt das nächste IAssemblyName-Objekt
		// von assemblyEnum holen
        hr = assemblyEnum.GetNextAssembly(
							(IntPtr)0,
							out fusionName,
							0);
        if (hr < 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
        // prüfen ob fusionName einen Wert hält
        if (fusionName != null)
        {
            // TODO: Fehlerbehandlung einbauen
            return getFullName(fusionName);
        }
        else
        {
            // keine Assembly mehr gefunden.
            done = true;
            return null;
        }
    }
    [SecurityPermission(
	 SecurityAction.LinkDemand,
	 UnmanagedCode = true)]
    private static string getFullName(IAssemblyName fusionAsmName)
    {
        int iLen = 1024;
        StringBuilder sDisplayName = new StringBuilder(iLen);
        int hr = fusionAsmName.GetDisplayName(
								sDisplayName,
								ref iLen,
								(int)AssemblyNameDisplayFlags.All);
        if (hr < 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
        return sDisplayName.ToString();
    }
}

Manch einem mag die Implementierung der Fusion-API sehr aufwendig erscheinen. Wenn sie für nur eine Anwendung gesehen wird, gebe ich ihm recht. Doch einmal als Bibliothek erstellt, ist sie für jedes zukünftige Projekt verfügbar oder kann auch weiter gegeben werden. Unter diesem Aspekt halte ich den Aufwand für gerechtfertigt.

Technorati-Tags: | | |
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitten "kicken" sie ihn.
kick it on dotnet-kicks.de