2

I've been researching this for a few days now. I'm trying to properly create an instance of a class within an external assembly under it's own AppDomain.

I've been able to load the external assembly under the new AppDomain, however all of its dependencies appear to load in the parent AppDomain which defeats the purpose, as I want to unload the dll's later to release the lock on them (plugin system). Any idea as to why it would be doing this?

public MyCustomObject BindAssembly()
{
    string currentAssemblyPath = @"C:\PathToMy\Assembly";
    string currentAssemblyFile = @"C:\PathToMy\Assembly\MyAssembly.dll";
    AssemblyName currentAssemblyName = AssemblyName.GetAssemblyName(currentAssemblyFile);
    AppDomain domain = AppDomain.CurrentDomain;
    domain.AssemblyResolve += domain_AssemblyResolve;
    AppDomainSetup setup = new AppDomainSetup()
    {
        PrivateBinPath = currentAssemblyPath,
        ApplicationBase = domain.BaseDirectory,
        DynamicBase = domain.SetupInformation.DynamicBase,
        ShadowCopyFiles = domain.SetupInformation.ShadowCopyFiles,
        CachePath = domain.SetupInformation.CachePath,
        AppDomainManagerAssembly = domain.SetupInformation.AppDomainManagerAssembly,
        AppDomainManagerType = domain.SetupInformation.AppDomainManagerType
    };
    AppDomain newDomain = AppDomain.CreateDomain("NewDomain", AppDomain.CurrentDomain.Evidence, setup);
    newDomain.AssemblyResolve += newDomain_AssemblyResolve;

    currentAssembly = newDomain.Load(currentAssemblyName);
    // tried this too
    //var obj = domain.CreateInstanceFromAndUnwrap(currentAssemblyFile, className);

    // list all of the assemblies inside the custom app domain
    var newDomainAssemblies = newDomain.GetAssemblies(); // (contains my loaded assembly, but not dependencies)

    // list all of the assemblies inside the parent app domain
    var appAssemblies = AppDomain.CurrentDomain.GetAssemblies(); // (contains my loaded assembly as well as all dependencies)

    return obj as MyCustomObject;
}

// resolve dependencies in custom domain
Assembly newDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    // this never fires
    AppDomain domain = (AppDomain)sender;
}

// resolve dependencies in parent domain
Assembly domain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    AppDomain domain = (AppDomain)sender;
    string assemblyDependencyPath = String.Format(@"{0}", Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(args.RequestingAssembly.CodeBase).Path)));
    string[] files = Directory.GetFiles(assemblyDependencyPath, "*.dll", SearchOption.AllDirectories);
    foreach (string file in files)
    {
        // if we found it, load the assembly and cache it.

        AssemblyName aname = AssemblyName.GetAssemblyName(file);
        if (aname.FullName.Equals(args.Name))
        {
            Assembly assembly = domain.Load(aname);
            //Assembly assembly = Assembly.LoadFrom(file);  // also tried this, same result
            return assembly;
        }
    }
}
  • have you tried `currentAssembly = newDomain.Load(currentAssemblyName);` instead of `currentAssembly = domain.Load(currentAssemblyName);`? – Rhumborl Sep 26 '14 at 21:39
  • I also confirmed that if I unload the new AppDomain I created, the dependent assemblies still remain in the parent AppDomain. – Michael Brown Sep 26 '14 at 21:39
  • Rhumborl - I apologize that was a typo in my stub code. Fixed in my question. – Michael Brown Sep 26 '14 at 21:40
  • You cannot refer to any types that are defined in the assembly that you loaded in the AppDomain. Like MyCustomObject. Use interfaces instead, defined in another assembly. Lots and lots of Google hits when you query ".net plugin architecture". – Hans Passant Sep 26 '14 at 21:40
  • Hans - the MyCustomObject is not defined in the loaded assembly, rather it is a abstract class that is defined in a seperate abstract assembly shared by both. – Michael Brown Sep 26 '14 at 21:42
  • It would appear that others are encountering this issue: http://stackoverflow.com/questions/11989896/load-assemblies-with-dependencies-in-a-different-appdomain – Michael Brown Sep 29 '14 at 18:41
  • Here is another example: http://weblog.west-wind.com/posts/2009/Jan/19/Assembly-Loading-across-AppDomains – Michael Brown Sep 29 '14 at 19:09

1 Answers1

1

I'm not sure how this (or cross-appdomain in general) will work with different implementations of an abstract class, because you may find the dependencies are still required to use the child class in your main AppDomain, but I had to do a similar thing recently and found that using a Serializable Loader class, which is in the same assembly as MyCustomObject, is the best way to keep things separate.

The idea is that the Loader is created in the current AppDomain then marshalled across to the new AppDomain and asked to load and instantiate the required assemblies and types there.

Note that both the Loader and anything inheriting from MyCustomObject must be Serializable or inherit from MarshalByRefObject as they will be passed between the AppDomains

public MyCustomObject BindAssembly()
{
    string currentAssemblyPath = @"C:\PathToMy\Assembly";
    string currentAssemblyFile = @"C:\PathToMy\Assembly\MyAssembly.dll";
    AssemblyName currentAssemblyName = AssemblyName.GetAssemblyName(currentAssemblyFile);
    AppDomain domain = AppDomain.CurrentDomain;
    domain.AssemblyResolve += domain_AssemblyResolve;
    AppDomainSetup setup = new AppDomainSetup()
    {
        PrivateBinPath = currentAssemblyPath,
        ApplicationBase = domain.BaseDirectory,
        DynamicBase = domain.SetupInformation.DynamicBase,
        ShadowCopyFiles = domain.SetupInformation.ShadowCopyFiles,
        CachePath = domain.SetupInformation.CachePath,
        AppDomainManagerAssembly = domain.SetupInformation.AppDomainManagerAssembly,
        AppDomainManagerType = domain.SetupInformation.AppDomainManagerType
    };

    AppDomain newDomain = AppDomain.CreateDomain("NewDomain", AppDomain.CurrentDomain.Evidence, setup);

    newDomain.Load(typeof(Loader).Assembly.GetName());

    Loader loader = (Loader)newDomain.CreateInstanceAndUnwrap(
        typeof(Loader).Assembly.FullName, typeof(Loader).FullName);

    // load the assembly containing MyCustomObject into the remote domain
    loader.LoadAssembly(currentAssemblyFile);

    // ask the Loader to create the object instance for us
    MyCustomObject obj = loader.CreateCustomObject();

    return obj;
}

public class Loader : MarshalByRefObject
{
    /// <summary>Stores the assembly containing the task class.</summary>
    private Assembly assembly;

    /// <summary>Retrieves the current lifetime service object that controls the lifetime policy for this instance.</summary>
    /// <returns>This always returns null.</returns>
    public override object InitializeLifetimeService()
    {
        return null;
    }

    /// <summary>Loads the assembly containing the task class.</summary>
    /// <param name="path">The full path to the assembly DLL containing the task class.</param>
    public void LoadAssembly(string path)
    {
        this.assembly = Assembly.Load(AssemblyName.GetAssemblyName(path));
    }

    /// <summary>Instantiates the required object.</summary>
    /// <param name="classFullName">The full name (namespace + class name) of the task class.</param>
    /// <returns>The new object.</returns>
    public MyCustomObject CreateCustomObject()
    {
        MyCustomObject instance = new MyCustomObject();
        // do whatever you want with the instance here
        return instance;
    }
}
Rhumborl
  • 16,349
  • 4
  • 39
  • 45
  • I did try something similiar to this using a proxy, however I reworked it to match your suggestion but it fails here with a could not load assembly error: Loader loader = (Loader)domain.CreateInstanceFromAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName); – Michael Brown Sep 26 '14 at 23:38
  • after some fiddling I realised I had to pass CreateInstanceFromAndUnwrap() the full path to the dll, however the behavior still exists that the dependencies get added to the parent AppDomain. – Michael Brown Sep 27 '14 at 00:17