C#, MVVM

Loading Assemblies In Separate Directories Into New AppDomain

As some of you may know I have been working on a code generator for my Cinch MVVM framework, which I am pleased to say I am nearly done with. The last stumbling block has been that I need to extract a bunch of Namespaces from Assemblies that the main code referenced, which I want to do by the use of Reflection which is very easy. But I also wanted the Assemblies that I would need to examine loaded into a new AppDomain so that I could Unload the newly created AppDomain when I had finished Reflecting out the Namespaces from the Assemblies.

After many failed attempted and messing around with

  • Asembly.ReflectionOnlyLoad
  • AppDomain.Load(Byte[] rawAssembly)
  • Fusion paths
  • Probing in my App.Config
  • AppDomain Evidence and AppDomainSetup

 

I finally found nirvana and hit the sweet spot, which is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
 
namespace ConsoleApplication1
{
 
    /// <summary>
    /// Loads an assembly into a new AppDomain and obtains all the
    /// namespaces in the loaded Assembly, which are returned as a 
    /// List. The new AppDomain is then Unloaded.
    /// 
    /// This class creates a new instance of a 
    /// <c>AssemblyLoader</c> class
    /// which does the actual ReflectionOnly loading 
    /// of the Assembly into
    /// the new AppDomain.
    /// </summary>
    public class SeperateAppDomainAssemblyLoader
    {
        #region Public Methods
        /// <summary>
        /// Loads an assembly into a new AppDomain and obtains all the
        /// namespaces in the loaded Assembly, which are returned as a 
        /// List. The new AppDomain is then Unloaded
        /// </summary>
        /// <param name="assemblyLocation">The Assembly file 
        /// location</param>
        /// <returns>A list of found namespaces</returns>
        public List<String> LoadAssemblies(List<FileInfo> assemblyLocations)
        {
            List<String> namespaces = new List<String>();
 
            AppDomain childDomain = BuildChildDomain(
                AppDomain.CurrentDomain);
 
            try
            {
                Type loaderType = typeof(AssemblyLoader);
                if (loaderType.Assembly != null)
                {
                    var loader = 
                        (AssemblyLoader)childDomain.
                            CreateInstanceFrom(
                            loaderType.Assembly.Location, 
                            loaderType.FullName).Unwrap();
 
                    namespaces = loader.LoadAssemblies(
                        assemblyLocations);
                }
                return namespaces;
            }
 
            finally
            {
 
                AppDomain.Unload(childDomain);
            }
        }
        #endregion
 
        #region Private Methods
        /// <summary>
        /// Creates a new AppDomain based on the parent AppDomains 
        /// Evidence and AppDomainSetup
        /// </summary>
        /// <param name="parentDomain">The parent AppDomain</param>
        /// <returns>A newly created AppDomain</returns>
        private AppDomain BuildChildDomain(AppDomain parentDomain)
        {
            Evidence evidence = new Evidence(parentDomain.Evidence);
            AppDomainSetup setup = parentDomain.SetupInformation;
            return AppDomain.CreateDomain("DiscoveryRegion", 
                evidence, setup);
        }
        #endregion
 
 
        /// <summary>
        /// Remotable AssemblyLoader, this class 
        /// inherits from <c>MarshalByRefObject</c> 
        /// to allow the CLR to marshall
        /// this object by reference across 
        /// AppDomain boundaries
        /// </summary>
        class AssemblyLoader : MarshalByRefObject
        {
            #region Private/Internal Methods
            /// <summary>
            /// ReflectionOnlyLoad of single Assembly based on 
            /// the assemblyPath parameter
            /// </summary>
            /// <param name="assemblyPath">The path to the Assembly</param>
            [SuppressMessage("Microsoft.Performance", 
                "CA1822:MarkMembersAsStatic")]
            internal List<String> LoadAssemblies(
                List<FileInfo> assemblyLocations)
            {
                List<String> namespaces = new List<String>();
                try
                {
                    foreach (FileInfo assemblyLocation in 
                        assemblyLocations)
                    {
                        Assembly.ReflectionOnlyLoadFrom(
                            assemblyLocation.FullName);
                    }
 
                    foreach (Assembly reflectionOnlyAssembly 
                        in AppDomain.CurrentDomain.
                            ReflectionOnlyGetAssemblies())
                    {
                        foreach (Type type in 
                            reflectionOnlyAssembly.GetTypes())
                        {
                            if (!namespaces.Contains(
                                type.Namespace))
                                namespaces.Add(
                                    type.Namespace);
                        }                   
                    }
                    return namespaces;
                }
                catch (FileNotFoundException)
                {
                    /* Continue loading assemblies even if an assembly
                     * can not be loaded in the new AppDomain. */
                    return namespaces;
                }
            }
            #endregion
        }
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Which you can use like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            SeperateAppDomainAssemblyLoader 
                appDomainAssemblyLoader = 
                new SeperateAppDomainAssemblyLoader();
 
            List<FileInfo> assemblies = new List<FileInfo>();
            String root = @"C:UserssachaDesktopAppDomainMonkery";
            assemblies.Add(new FileInfo(root + 
                @"ConsoleApplication1ClassLibrary1binDebugClassLibrary1.dll"));
            assemblies.Add(new FileInfo(root + 
                @"ConsoleApplication1ClassLibrary2binDebugClassLibrary2.dll"));
 
            foreach (String @namespace in 
                appDomainAssemblyLoader.
                    LoadAssemblies(assemblies))
            {
                Console.WriteLine(String.Format(
                    "Namespace found : {0}", @namespace));
            }              
            Console.ReadLine();
        }
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Which as you can see results in the following :

image

 

I think this code is pretty useful and I hope you find as much use for it as I have. It took me long enough to figure this out, and many google searches were done and much consulting of APIs/picking friends knowledge was done to bring you this code, so enjoy it. It nearly killed me getting that one to work.

As usual here is a small demo app : http://sachabarber.net/wp-content/uploads/2009/09/AppDomainMonkery.zip

29 thoughts on “Loading Assemblies In Separate Directories Into New AppDomain

    1. Looks interesting Ill have a look, but generally I prefer native .NET I write. I have more control

  1. I tried the your example and it works fine. However when I tried t0 add a dll with a windows form in it it didn’t work. Any idea?

    1. NO I have no idea why that would be actually, maybe its a references thimg. I am on holiday now so cant try it, try and add a reference to winforms in your main app see if that helps. Obviously it is not what you are looking for but may provide some answer at least

  2. I tried with sevaral changes but none of them worked with class libraries with forms. The behaviour was like this.

    I created class library with winForm and the generated dll failed to load into the child domain. However when I remove the winForm from the class library it loads and works fine.

    However with winForm in when the code executes

    foreach (Type type in reflectionOnlyAssembly.GetTypes())

    It throws an exception.

    The exception it generates is “Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information”

    Please help

  3. Sorry I made a mistake in my above comment. The dll indeed loads into the child domain but fails at GetTypes()

  4. It’s so accdental that I just write such a tool for myself before I read you article, the tool I made not only reflect the namespace, but also the classes, interfaces, and it’s members, and I also want to draw a diagram to show the class inherit relationships, but it is not finished.

  5. A solution at long last! I eventually had to give up, cursing Microsoft for making what IMHO should have been easy to accomplish with a tiny handful of method calls.

    Thank you!

  6. Partly like your work, the work I do only use my spare time, I am busy in my work time, so I am not very clear about the tool will be finally.
    By the way I have a question, that is what is the advantages of load assemblies in a separate AppDomain? One I know is that you can unload it. Can this way save memory or any other advantages?

  7. After spending many many hours I found the solution for my problem.

    Just add
    AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);

    problem had been not loading the referenced dll’s by the class library containing winforms

  8. Hi Sacha, sorry for the late comment but maybe you can help for an adjacent task.

    As you (and me) found out, loading assemblies into (different) appdomains and using them seem to be a tricky task.

    What is missing here in my opinion is the real usage of types within the loaded assemblies instead of pure reflection (or was it in one of the other chinch parts?). Moreover, as Prasanna said, if a loaded assembly requires futher dependent assemblies you have to manage the dependant assembly loading by yourself via

    .[ReflectionOnly]AssemblyResolve += new ;

    which you clearly omitted in above stated coding and placed a catch instead.

    Like in http://www.codeproject.com/KB/DLL/DynamicUserDLL.aspx, where you left some comments, it is generally more interesting to find and instantiate the unknown(!) type in an assembly, loaded into another appdomain, implementing a pre-given interface (latter presumably also loaded in the main appdomain).

    Unfortunately this does not seem to stick together:
    The cause for this is that methods like CreateInstance[From][…] require a type (i.e. the implementing class) name instead of the interface name which one do not know in before. One would have to use the ReflectionOnly mechanism to query the type (name) from the loaded assembly but this requires to implement above mentioned handler for all possible types dependant assemblies (including native libraries).

    This is a mess to cope with (omitting the issues with the diverse load contexts). Maybe you have become aware of some more sophisticated approaches in response to your blog and codeguru entries and can share them?!

    Thanks in advance. Regards FGRT

  9. Sorry, the code was meant to show:

    <AppDomain-Instance>.[ReflectionOnly]AssemblyResolve += new <your handler>;

  10. Hi Sacha.

    With some modifications at least your link was a source of help. I do not know if addicting to MarshalByRefObject and LoadFrom will be of future problems but until know it just works as desired.

    Thanks a lot. Regards FGRT.

  11. Just wanted to thank you for posting this information. This page is, to my knowledge, the only complete solution that demonstrates assembly loading in a child AppDomain without having to first load the assembly in the parent AppDomain, or having to create an event handler for the ‘AssemblyResolve’ event. Your method allows true encapsulation of assemblies within the child AppDomain.

    Again, thanks. This information saved me a lot of time and trouble.

    Dan

    1. Daniel

      That is really nice to hear, it was a bugger to get working, so it is nice to hear that it helped other people.

  12. Hello Shacha,

    It’s a nice post and that can be help me at my current project.
    Using this code, how can I execute an specific method from an assembly? For example, I want to execute the method GetStyffXXXX() from Class1 in my host application.

    Is that possible ?

    Thanks

    1. In the loader you would use Reflection to find that Type, Get its constructor using Reflection and create new instance of that object by calling the constructor using Refection (or use Activator.CreateInstance) then simply call the method you want.

  13. Thanks Sacha for the wonderful post.

    I was struggling for several weeks and your post saved me with loading assemblies into appdomain. Nice and clean.

    Thanks.

    1. Helmut

      I had to change blogs and some stuff got lost during the process. I don’t think I have this available any more. So sorry about that

    1. I can’t that stuff is now lost, or used to self body my blog and it got corrupted.

      If you are looking for cinch code generator that is available at cinch.codeplex.com, if you want the assembly loading stuff cinch code generator had tat inside it too.

      Best I can do sorry

Leave a comment