Linq to Gac : Use Linq to Power Query your Gac via this C# Bridge to Fusion

your gac

kick it on DotNetKicks.com

Digg This

This post gets you Linq access to the GAC with no fuss.

Fusion is wrapped for you so no need to understand the unmanaged stuff unless you want to. Your up and running with GAC as a new Linq to Objects source in minutes.

The Big Picture of the Project (You can skip to the next section if you just want the GAC code)

Note: The reason both the Gac and MyEnvironment IEnumerable<T> references below are different? See the reference on the bottom right of the picture into the entry point to all of it (ThisBox)?

ClassDiagram1

Introduction to the Fusion Wrapped IEnumerable

A simple case. I want to know how my GAC is broken into ‘Major Vendors’. We assume this is roughly the first part of the name. (i.e. Microsoft.System = Microsoft).

Oh yeah. Exclude the assemblies that only occur once. We are looking for the larger view here.

Here is an easy solution:

        [Fact]
        public void should_show_gac_by_vendor()
        {
            var result = from matchedAssembly in ThisBox.By<AssemblyDetail>()
                         group matchedAssembly by
                             matchedAssembly.MajorName
                             into grouping
                             where grouping.Count() > 1
                             orderby grouping.Count() descending
                             select grouping;

            var TotalSum = result.Sum(ad => ad.Count());

            Console.WriteLine("** ASSEMBLIES BY NAME WITH > 1 REGISTERED  = {0} ***", TotalSum);

            foreach (var firstGroup in result)
            {
                var groupCount = firstGroup.Count();
                Console.WriteLine("Group '{0}' Assemblies={1} Percent={2} Public Keys Used={3}",
                                    firstGroup.Key,
                                    groupCount,
                                    groupCount.ToPctString(TotalSum),
                                    firstGroup.Select(ad => ad.PublicKey).Distinct().Count()
                                    );
            }
        }

** ASSEMBLIES BY NAME WITH > 1 REGISTERED  = 664 ***

Group ‘Microsoft’ Assemblies=466 Percent=70.18% Public Keys Used=5
Group ‘DevExpress’ Assemblies=73 Percent=10.99% Public Keys Used=2
Group ‘System’ Assemblies=71 Percent=10.69% Public Keys Used=4
Group ‘Policy’ Assemblies=14 Percent=2.11% Public Keys Used=2
Group ‘FSharp’ Assemblies=11 Percent=1.66% Public Keys Used=1
Group ‘VLinq’ Assemblies=10 Percent=1.51% Public Keys Used=1
Group ‘policy’ Assemblies=6 Percent=0.9% Public Keys Used=2
Group ‘PresentationFramework’ Assemblies=5 Percent=0.75% Public Keys Used=1
Group ‘mscorcfg’ Assemblies=2 Percent=0.3% Public Keys Used=1
Group ‘WebDev’ Assemblies=2 Percent=0.3% Public Keys Used=1
Group ‘VSTADTEProvider’ Assemblies=2 Percent=0.3% Public Keys Used=1
Group ‘VsWebSite’ Assemblies=2 Percent=0.3% Public Keys Used=1

Try that with GacUtil (grin)..

C# GAC IEnumerable<Assembly> implementation

Ah yes the GAC Fusion wrapper code.  Thanks to Debasish Bose for his help here.

C# Fusion Gac Enumerable : Domain.Dot.Net Team

ClassDiagram2

    /// <summary>
    /// Just to have some room to breath
    /// </summary>
    public class AssemblyDetail : AssemblyDetailBase
    {
        public AssemblyDetail(IAssemblyName currentAssembly) : base(currentAssembly) {}

        public override String FullName
        {
            get
            {
                return _fullName;
            }
        }
        public override string Version
        {
            get
            {
                return _versionString;
            }
        }
        public override int Major
        {
            get
            {
                return VersionComponent(0);
            }
        }
        public override int Minor
        {
            get
            {
                return VersionComponent(1);
            }
        }
        public override int Revision
        {
            get
            {
                return VersionComponent(2);
            }
        }
        public override int Build
        {
            get
            {
                return VersionComponent(3);
            }
        }

        public override string PublicKey
        {
            get
            {
                return _pkey;
            }
        }
        public override string MajorName
        {
            get
            {
                return _majorName;
            }
        }
        public String Name
        {
            get
            {
                return _fullName;
            }
        }

        int VersionComponent(int position)
        {
            return _version.Length <= position + 1
                           ? Convert.ToInt32(_version[position]) : 0;
        }
    }

    #region

    using System;
    using System.Runtime.InteropServices;

    #endregion

    /// <summary>
    /// Alpha - Comment and Add Regressions
    /// </summary>
    public abstract class AssemblyDetailBase
    {
        protected string _fullName;
        protected string _majorName;
        string[] _nameSplit;
        protected string _pkey;
        string _rawname;
        protected string[] _version;
        protected string _versionString;

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyDetailBase"/> class.
        /// </summary>
        /// <param name="Current">The current.</param>
        protected AssemblyDetailBase(IAssemblyName Current)
        {
            ExtractParsedDetail(Current);
        }

        public abstract string FullName
        {
            get;
        }
        public abstract string Version
        {
            get;
        }
        public abstract int Major
        {
            get;
        }
        public abstract int Minor
        {
            get;
        }
        public abstract int Revision
        {
            get;
        }
        public abstract int Build
        {
            get;
        }
        public abstract string PublicKey
        {
            get;
        }
        public abstract string MajorName
        {
            get;
        }

        /// <summary>
        /// I do not want to persist the IAssemblyName
        /// as it might introduce a COM reference when
        /// we just can extract what we need and let it
        /// pass away into whatever...
        ///
        /// Extracts the parsed detail.
        /// </summary>
        /// <param name="Current">The current.</param>
        void ExtractParsedDetail(IAssemblyName Current)
        {
            const int stongKeyPosition = 3;
            const int versionPosition = 1;

            // Main Work Happens Here
            _fullName = GetDisplayName(Current);

            // Simply parse the ", " that seperates the full
            // name
            var _parsedDetails = _fullName.Split(new[]
                                                 {
                                                         ", "
                                                 }, StringSplitOptions.RemoveEmptyEntries);

            ExtractVersionInfo(versionPosition, _parsedDetails);
            ExtractAddition(stongKeyPosition, _parsedDetails);
        }

        void ExtractAddition(int stongKeyPosition, string[] parsedDetails)
        {
            _pkey = parsedDetails[stongKeyPosition];
            _rawname = parsedDetails[0].Trim();
            _nameSplit = _rawname.Split('.');
            _majorName = _nameSplit[0].Trim();
        }

        /// <summary>
        /// Gets the display name from the unmanaged pointer
        /// </summary>
        /// <param name="aName">A name.</param>
        /// <returns></returns>
        static string GetDisplayName(IAssemblyName aName)
        {
            var displayFlags = AssemblyNameDisplayFlags.ALL;

            uint uiLen = 0;

            string _displayAssemblyName = null;

            // Get the length
            aName.GetDisplayName(IntPtr.Zero, ref uiLen, (uint) displayFlags);

            if (uiLen > 0)
            {
                var byteBuffer = new byte[(uiLen + 1)*2];
                unsafe //turn on sunsafe in project properties
                {
                    fixed (byte* numRef1 = byteBuffer)
                    {
                        var ptr1 = new IntPtr(numRef1);
                        aName.GetDisplayName(ptr1, ref uiLen, (uint) displayFlags);
                        _displayAssemblyName = Marshal.PtrToStringUni(ptr1);
                    }
                }
            }

            return _displayAssemblyName;
        }

        /// <summary>
        /// Extracts the version info.
        /// </summary>
        /// <param name="versionPosition">The version position.</param>
        /// <param name="parsedDetails">The parsed details.</param>
        void ExtractVersionInfo(int versionPosition, string[] parsedDetails)
        {
            var versionLiteral = parsedDetails[versionPosition];
            _versionString = ToCoreVersion(versionPosition, versionLiteral);
            if (_versionString != String.Empty)
                _version = _versionString.Split('.');
        }

        /// <summary>
        /// Gets the nice clean x.x.x.x string
        /// </summary>
        /// <param name="versionPosition">The version position.</param>
        /// <param name="versionLiteral">The version literal.</param>
        /// <returns></returns>
        static string ToCoreVersion(int versionPosition, string versionLiteral)
        {
            var returnVal = String.Empty;

            if (!String.IsNullOrEmpty(versionLiteral))
            {
                var _indexOf = versionLiteral.IndexOf("=");

                if (_indexOf != -1)
                    returnVal = versionLiteral.Substring(_indexOf + versionPosition);
            }

            return returnVal;
        }
    }

#region

using System.Runtime.InteropServices;

#endregion

class GAC
{
    GAC() {}

    [DllImport("Fusion.dll", CharSet = CharSet.Auto)]
    public static extern int CreateAssemblyEnum(out IAssemblyEnum ppEnum,
                                                IApplicationContext pAppCtx,
                                                IAssemblyName pName, uint dwFlags,
                                                int pvReserved);
}

#region

using System.Runtime.InteropServices;
using System.Security;

#endregion

[SuppressUnmanagedCodeSecurity, ComImport, Guid("7C23FF90-33AF-11D3-95DA-00A024A85B51"),
 InterfaceType(1)]
public interface IApplicationContext
{
    void SetContextNameObject(IAssemblyName pName);

    void GetContextNameObject([Out] out IAssemblyName ppName);

    void Set([MarshalAs(UnmanagedType.LPWStr)] string szName, int pvValue, uint cbValue,
             uint dwFlags);

    void Get([MarshalAs(UnmanagedType.LPWStr)] string szName, [Out] out int pvValue,
             ref uint pcbValue, uint dwFlags);

    void GetDynamicDirectory([Out] out int wzDynamicDir, ref uint pdwSize);
}

#region

using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;

#endregion

/// <summary>
/// IAssemblyName from Fusion
/// </summary>
[SuppressUnmanagedCodeSecurity, ComImport, Guid("CD193BC0-B4BC-11D2-9833-00C04FC31D2E"),
 InterfaceType(1)]
public interface IAssemblyName
{
    [PreserveSig]
    int SetProperty(uint PropertyId, IntPtr pvProperty, uint cbProperty);

    [PreserveSig]
    int GetProperty(uint PropertyId, IntPtr pvProperty, ref uint pcbProperty);

    [PreserveSig]
    int Finalize();

    [PreserveSig]
    int GetDisplayName(IntPtr szDisplayName, ref uint pccDisplayName, uint dwDisplayFlags);

    [PreserveSig]
    int BindToObject(object refIID, object pAsmBindSink,
                     IApplicationContext pApplicationContext,
                     [MarshalAs(UnmanagedType.LPWStr)] string szCodeBase, long llFlags,
                     int pvReserved, uint cbReserved, [Out] out int ppv);

    [PreserveSig]
    int GetName(ref uint lpcwBuffer,
                [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwzName);

    [PreserveSig]
    int GetVersion([Out] out uint pdwVersionHi, [Out] out uint pdwVersionLow);

    [PreserveSig]
    int IsEqual(IAssemblyName pName, uint dwCmpFlags);

    [PreserveSig]
    int Clone([Out] out IAssemblyName pName);
}

#region

using System.Runtime.InteropServices;
using System.Security;

#endregion

[SuppressUnmanagedCodeSecurity, ComImport, Guid("21B8916C-F28E-11D2-A473-00C04F8EF448"),
 InterfaceType(1)]
interface IAssemblyEnum
{
    [PreserveSig]
    int GetNextAssembly([Out] out IApplicationContext ppAppCtx,
                        [Out] out IAssemblyName ppName, uint dwFlags);

    [PreserveSig]
    int Reset();

    [PreserveSig]
    int Clone([Out] out IAssemblyEnum ppEnum);
}

#region

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

#endregion

/// <summary>
/// Fairly basic stuff once the
/// unmanaged stuff is all mapped
/// </summary>
public class GacEnumerable : IEnumerator<IAssemblyName>, IEnumerable<IAssemblyName>
{
    readonly IAssemblyEnum _enumeration;
    IAssemblyName _enumCurrentAssembly;

    GacEnumerable()
    {
        HandleCom(GAC.CreateAssemblyEnum(out _enumeration, null, null,
                                         (uint) AssemblyCacheFlags.GAC, 0));
    }

    public static GacEnumerable New
    {
        get
        {
            return new GacEnumerable();
        }
    }

    #region IEnumerable<IAssemblyName> Members

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
    /// </returns>
    public IEnumerator<IAssemblyName> GetEnumerator()
    {
        return this;
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
    /// </returns>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    #region IEnumerator<IAssemblyName> Members

    /// <summary>
    /// Advances the enumerator to the next element of the collection.
    /// </summary>
    /// <returns>
    /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
    /// </returns>
    /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
    bool IEnumerator.MoveNext()
    {
        IApplicationContext context1;
        return (0 ==
                _enumeration.GetNextAssembly(out context1, out _enumCurrentAssembly, 0));
    }

    /// <summary>
    /// Sets the enumerator to its initial position, which is before the first element in the collection.
    /// </summary>
    /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
    void IEnumerator.Reset()
    {
        _enumeration.Reset();
    }

    /// <summary>
    /// IAssemblyName is a Fusion interface
    /// Gets the element in the collection at the current position of the enumerator.
    /// </summary>
    /// <value></value>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    public IAssemblyName Current
    {
        get
        {
            return _enumCurrentAssembly;
        }
    }
    // Properties
    object IEnumerator.Current
    {
        // Go to the other darn Current not this one
        get
        {
            return Current;
        }
    }
    // Fields
    public void Dispose()
    {
        _enumCurrentAssembly = null;
    }

    #endregion

    internal static void HandleCom(int hResult)
    {
        if (hResult < 0)
            Marshal.ThrowExceptionForHR(hResult);
    }
}

[Flags]
enum AssemblyCacheFlags
{
    NGenCache = 0x1,
    GAC = 0x2,
    DownloadCache = 0x4
}


[Flags]
public enum AssemblyNameDisplayFlags
{
    VERSION = 0x01,
    CULTURE = 0x02,
    PUBLIC_KEY_TOKEN = 0x04,
    PROCESSORARCHITECTURE = 0x20,
    RETARGETABLE = 0x80,
    ALL = VERSION |
        CULTURE | PROCESSORARCHITECTURE |
        PUBLIC_KEY_TOKEN | RETARGETABLE
}

More to Come

This is the tip of the iceberg so to speak. Here is a test case to show you what this now is doing and perhaps you can give us ideas where to take it. There is a lot of code. Should it be up on CodePlex? Google Code? Your Environment Variables:

       [Fact]
        public void should_show_my_env()
        {
            var sw = Stopwatch.StartNew();
            var dirs = from env in ThisBox.By<EnvironmentVariable>()
                       select env;
            foreach (var s in dirs)
            {
                Console.WriteLine(s.TypeOfEnv + " " + s.KeyName);
                Console.WriteLine("  ==>" + s.Value);
            }
            Console.WriteLine("Count : " + dirs.Count());
            Console.WriteLine("Search Took : " + sw.ElapsedMilliseconds + " ms");
            sw.Stop();
        }

Let’s join our GAC with our registry!

NOTE: we get 592 hits in 20 or so seconds…

        [Fact]
        public void should_join_policy_registry_section()
        {
            var sw = Stopwatch.StartNew();

            var result = from
                             gac in ThisBox.By<AssemblyDetail>()
                         join
                             registryKey in ThisBox.By<RegistryKey>() 

                         on gac.MajorName equals registryKey.FirstRegValue()

                         select new
                                {
                                        gac, RegVal=registryKey.FirstRegValue()
                                };

            Console.WriteLine("Count : " + result.Count());
            sw.Stop();
            foreach (var a in result)
            {
                Console.WriteLine(a.gac.MajorName + " == " + a.RegVal);
                Console.WriteLine(" ==>" + a.gac.FullName);
            }

            Console.WriteLine("Search Took : " + sw.ElapsedMilliseconds + " ms");
        }

Appendix : A Few Extension Methods we Use

  static class LinqProviderTestExtensions
    {
        /// <summary>
        /// Will perform each action for each
        /// 'row' in the included collection
        /// Each 'row' is of Type TTreeNode
        /// Will typically only be one action
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="actions"></param>
        static internal void ForEach<T>(this IEnumerable<T> items, params Action<T>[] actions)
        {
            foreach (var t in items)
                foreach (var act in actions)
                    act.Invoke(t);
        }

        public static string FirstRegValue(this RegistryKey registryKey)
        {
            return registryKey.ValueCount > 0
                           ? (string) registryKey.GetValueNames().GetValue(0) : String.Empty;
        }

        public static double ToPercent(this int target, int total)
        {
            return target.ToPercent((double) total);
        }

        public static String ToPctString(this int target, int total)
        {
            return target.ToPercent((double) total) + "%";
        }

        public static double ToPercent(this int target, double total)
        {
            return Math.Round((target/total)*100, 2);
        }
    }
A huge Thanks:
Debasish Bose


Related Posts

Digg This

~ by Damon Wilder Carr on 09.20.08.

2 Responses to “Linq to Gac : Use Linq to Power Query your Gac via this C# Bridge to Fusion”

  1. [...] here: ‘Linq to Gac’ : Use Linq to Power Query your Gac via this C# Bridge to Fusion collection, concepts-implemented, damon-wilder, fusion, global-assembly-cache, group, group-total, [...]

  2. [...] Linq to Gac : Use Linq to Power Query your Gac via this C# Bridge to Fusion « team domain.dot.net [...]

Leave a Reply