Click here to Skip to main content
15,896,111 members
Articles / Operating Systems / Windows

Sandcastle Help File Builder

Rate me:
Please Sign up or sign in to vote.
4.93/5 (131 votes)
17 May 2007Ms-PL45 min read 1M   5.3K   291  
A GUI for creating projects to build help files with Sandcastle and a console mode tool to build them as well.
//=============================================================================
// System  : Sandcastle Help File Builder Utilities
// File    : AssemblyLoader.cs
// Author  : Eric Woodruff
// Updated : 01/20/2007
// Note    : Copyright 2006-2007, Eric Woodruff, All rights reserved
// Compiler: Microsoft Visual C#
//
// This file contains a class that is used to load assemblies in a separate
// application domain in order to obtain a list of namespaces within them.
// It also contains a utility method used to get a list of assemblies in the
// GAC.
//
// This code may be used in compiled form in any way you desire.  This file
// may be redistributed unmodified by any means PROVIDING it is not sold for
// profit without the author's written consent, and providing that this notice
// and the author's name and all copyright notices remain intact.
//
// This code is provided "as is" with no warranty either express or implied.
// The author accepts no liability for any damage or loss of business that
// this product may cause.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.2.0.0  08/04/2006  EFW  Created the code
//=============================================================================

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

using SandcastleBuilder.Utils;

namespace SandcastleBuilder.Utils.Gac
{
    /// <summary>
    /// This class is used to load assemblies in a separate application
    /// domain in order to obtain a list of namespaces within them.  It also
    /// contains a utility method used to get a list of assemblies in the GAC.
    /// </summary>
    /// <remarks>By loading them in a separate application domain, we can
    /// unload the application domain when done and not hold a reference
    /// to the assemblies.</remarks>
    public class AssemblyLoader : System.MarshalByRefObject
    {
        //=====================================================================
        // Private data members

        AppDomain appDomain;
        Dictionary<string, Assembly> loadedAssemblies;
        Collection<string> dependencies;

        // The one and only assembly loader
        private static AssemblyLoader loader;

        // This will contain the GAC assembly information.  It can be a little
        // time-consuming to enumerate the GAC so we'll keep the list around
        // after the first enumeration.
        private static Collection<string> gacList;

        //=====================================================================
        // Static properties and methods

        /// <summary>
        /// This static property is used to obtain a list containing the
        /// fully qualified names of all assemblies in the GAC.
        /// </summary>
        public static Collection<string> GacList
        {
            get
            {
                IAssemblyEnum ae;
                IAssemblyName an;
                StringBuilder sb;
                uint chars;

                if(gacList == null)
                {
                    gacList = new Collection<string>();

                    try
                    {
                        NativeMethods.CreateAssemblyEnum(out ae, IntPtr.Zero,
                            null, ASM_CACHE_FLAGS.ASM_CACHE_GAC, IntPtr.Zero);

                        while(ae.GetNextAssembly(IntPtr.Zero, out an, 0) == 0)
                        {
                            chars = 0;
                            an.GetDisplayName(null, ref chars,
                                ASM_DISPLAY_FLAGS.ALL);
                            sb = new StringBuilder((int)chars);
                            an.GetDisplayName(sb, ref chars,
                                ASM_DISPLAY_FLAGS.ALL);

                            gacList.Add(sb.ToString());
                        }
                    }
                    catch(Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.ToString());
                        throw new BuilderException(
                            "Unable to enumerate the GAC", ex);
                    }
                }

                return gacList;
            }
        }

        /// <summary>
        /// This is used to create the assembly loader instance.
        /// </summary>
        /// <param name="project">The project from which to get the
        /// necessary dependency information.</param>
        /// <returns>Returns the assembly loader instance</returns>
        public static AssemblyLoader CreateAssemblyLoader(
          SandcastleProject project)
        {
            string[] files;
            string dirName, fileSpec, sourcePath;
            int idx;

            if(loader == null)
            {
                AppDomain appDomain = AppDomain.CreateDomain(
                    "SHFBNamespaceInfo");

                loader = (AssemblyLoader)appDomain.CreateInstanceAndUnwrap(
                    typeof(AssemblyLoader).Assembly.FullName,
                    typeof(AssemblyLoader).FullName, false,
                    BindingFlags.NonPublic | BindingFlags.Instance, null,
                    new object[0], CultureInfo.InvariantCulture,
                    new object[0], AppDomain.CurrentDomain.Evidence);

                loader.AppDomain = appDomain;

                foreach(DocumentAssembly da in project.Assemblies)
                    if(!da.CommentsOnly)
                        loader.AddDependency(da.AssemblyPath);

                foreach(DependencyItem di in project.Dependencies)
                {
                    sourcePath = di.DependencyPath;

                    if(!sourcePath.StartsWith("GAC:"))
                        if(sourcePath.IndexOfAny(new char[] { '*', '?' }) == -1)
                            loader.AddDependency(sourcePath);
                        else
                        {
                            idx = sourcePath.LastIndexOf('\\');
                            dirName = sourcePath.Substring(0, idx);
                            fileSpec = sourcePath.Substring(idx + 1);

                            files = Directory.GetFiles(dirName, fileSpec);

                            foreach(string name in files)
                                if(name.EndsWith(".dll") || name.EndsWith(".exe"))
                                    loader.AddDependency(name);
                        }
                }
            }

            return loader;
        }

        /// <summary>
        /// This is used to release the assembly loader instance.
        /// </summary>
        public static void ReleaseAssemblyLoader()
        {
            if(loader != null && loader.AppDomain != null)
                AppDomain.Unload(loader.AppDomain);

            loader = null;
        }

        //=====================================================================
        // Properties

        /// <summary>
        /// Set or get the application domain for this instance
        /// </summary>
        private AppDomain AppDomain
        {
            get { return appDomain; }
            set { appDomain = value; }
        }

        //=====================================================================
        // Methods, etc

        /// <summary>
        /// Private constructor
        /// </summary>
        /// <remarks>Use <see cref="CreateAssemblyLoader" /> to create
        /// an instance.  Use <see cref="ReleaseAssemblyLoader"/> when
        /// done with it.</remarks>
        private AssemblyLoader()
        {
            dependencies = new Collection<string>();
            loadedAssemblies = new Dictionary<string, Assembly>();

            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(
                CurrentDomain_AssemblyResolve);
        }

        /// <summary>
        /// This is used to get the location of the specified GAC assembly
        /// </summary>
        /// <param name="fullName">The assembly name in long form</param>
        /// <returns>Returns the full file path for the GAC assembly</returns>
        public string GetAssemblyLocation(string fullName)
        {
            foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies())
                if(a.FullName == fullName)
                    return a.Location;

            // If not found, load it now
            Assembly asm = Assembly.Load(fullName);
            return asm.Location;
        }

        /// <summary>
        /// Add a dependency to the dependencies collection
        /// </summary>
        /// <param name="dependencyPath">The path to the dependency</param>
        /// <remarks>We must add the dependency within the instance as you
        /// cannot add them in the calling code as they won't be seen
        /// here in the application domain in which the instance is hosted.
        /// </remarks>
        public void AddDependency(string dependencyPath)
        {
            dependencies.Add(Path.GetFullPath(dependencyPath));
        }

        /// <summary>
        /// Load the named assembly file and get a list of all of its
        /// namespaces.
        /// </summary>
        /// <param name="assemblyFilename">The name of the assembly</param>
        /// <returns>Returns a string collection containing the namespace
        /// names.</returns>
        public Collection<string> GetNamespaces(string assemblyFilename)
        {
            Collection<string> namespaces = new Collection<string>();
            Assembly asm = null;
            string ns;

            // Do we need to load it now?
            if(!loadedAssemblies.TryGetValue(assemblyFilename, out asm))
            {
                AssemblyName asmName = AssemblyName.GetAssemblyName(
                    assemblyFilename);

                foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies())
                    if(asmName.FullName == a.FullName)
                    {
                        asm = a;
                        break;
                    }

                if(asm == null)
                    asm = Assembly.LoadFile(assemblyFilename);

                if(asm == null)
                    throw new ArgumentException("Unable to load assembly: " +
                        assemblyFilename, "assemblyFilename");

                loadedAssemblies.Add(assemblyFilename, asm);
            }

            // Get the unique namespaces from the types in the assembly
            foreach(Type t in asm.GetTypes())
            {
                ns = (t.Namespace == null) ? String.Empty : t.Namespace;

                if(!namespaces.Contains(ns))
                    namespaces.Add(ns);
            }

            return namespaces;
        }

        /// <summary>
        /// This is handled to resolve dependent assemblies and load them
        /// when necessary.
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="args">The event arguments</param>
        /// <returns>The loaded assembly</returns>
        private Assembly CurrentDomain_AssemblyResolve(object sender,
          ResolveEventArgs args)
        {
            Assembly asm = null;

            string[] nameInfo = args.Name.Split(new char[] { ',' });
            string resolveName = nameInfo[0];

            // See if it has already been loaded
            foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies())
                if(args.Name == a.FullName)
                {
                    asm = a;
                    break;
                }

            // Check the dependencies list?
            if(asm == null)
                foreach(string filename in dependencies)
                    if(resolveName == Path.GetFileNameWithoutExtension(filename))
                    {
                        asm = Assembly.LoadFile(filename);
                        loadedAssemblies.Add(filename, asm);
                        break;
                    }

            if(asm == null)
                throw new BuilderException("Unable to resolve reference to " +
                    args.Name);

            return asm;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer (Senior)
United States United States
Eric Woodruff is an Analyst/Programmer for Spokane County, Washington where he helps develop and support various applications, mainly criminal justice systems, using Windows Forms (C#) and SQL Server as well as some ASP.NET applications.

He is also the author of various open source projects for .NET including:

The Sandcastle Help File Builder - A front end and project management system that lets you build help file projects using Microsoft's Sandcastle documentation tools. It includes a standalone GUI and a package for Visual Studio integration.

Visual Studio Spell Checker - A Visual Studio editor extension that checks the spelling of comments, strings, and plain text as you type or interactively with a tool window. This can be installed via the Visual Studio Gallery.

Image Map Controls - Windows Forms and web server controls that implement image maps.

PDI Library - A complete set of classes that let you have access to all objects, properties, parameter types, and data types as defined by the vCard (RFC 2426), vCalendar, and iCalendar (RFC 2445) specifications. A recurrence engine is also provided that allows you to easily and reliably calculate occurrence dates and times for even the most complex recurrence patterns.

Windows Forms List Controls - A set of extended .NET Windows Forms list controls. The controls include an auto-complete combo box, a multi-column combo box, a user control dropdown combo box, a radio button list, a check box list, a data navigator control, and a data list control (similar in nature to a continuous details section in Microsoft Access or the DataRepeater from VB6).

For more information see http://www.EWoodruff.us

Comments and Discussions