4
votes

I'm trying to load assemblies into another appdomain (to be able to unload them when not needed) but also I want to be able to inspect types in the loaded assembly and create instances of my types (via Activator). Also I don't want the assemblies to get locked when loaded.

I don't see how this is possible. Things I have tried:

  • Loading Assembly.Load(File.ReadAllBytes(path)) helps me load an assembly without locking it, so it can get deleted/moved. This loads assembly into the current appdomain, so it won't be possible to unload it.

  • Creating another AppDomain and using AppDomain.Load() loads the assembly into a new AppDomain, but it is not possible to check all the types that are in the loaded AppDomain. I can't create anything, unless I know type fully qualified type name, and even then they have to either be Serializable or derive from MarshallByRef. Another AppDomain stuff just works via Proxies so it is harder to create things without having a Contract/Shared Interface, etc.

  • MEF also loads assemblies into the current AppDomain, so it is basically doing the same thing.

  • Mono.Cecil allows type inspection on a loaded assembly, but then I can't create types using TypeReference. Maybe if there was a way to convert TypeReference to Type?

I have checked how ILSpy does this, and to no surpirse it uses Mono.Cecil to load/unload the assembly, but then again, it does not create instances of any types, just does the type inspection which is possible going via Mono.Cecil route.

Now the question is, is this even possible? Am I missing anything?

1

1 Answers

1
votes

If you load the assembly into another appdomain then you should of course create instances there and use them there. Other than that, not sure what problem you have run into.

Updates

  1. added code to pick type from list
  2. changed to only pass type names across the domains as strings

using System;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;

public class MainClass : MarshalByRefObject
{
    static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("usage: {0} assembly", Path.GetFileName(Assembly.GetExecutingAssembly().Location));
            return;
        }
        AppDomain other = AppDomain.CreateDomain("other");
        Type myType = typeof(MainClass);
        // create a loader instance in the new domain
        MainClass loader = (MainClass)other.CreateInstanceAndUnwrap(myType.Assembly.FullName, myType.FullName);
        string assemblyName;
        string[] types = loader.LoadAssembly(args[0], out assemblyName);
        Console.WriteLine("Loaded assembly {0}.", assemblyName);
        Console.WriteLine("Types:");
        for(int i = 0; i < types.Length; i += 1)
        {
            Console.WriteLine("[{0}] {1}", i, types[i]);
        }
        Console.Write("Enter index of type to create, -1 to exit: ");
        int create = Int32.Parse(Console.ReadLine());
        if (create < 0)
        {
            return;
        }
        Console.WriteLine("Creating instance of type {0}", types[create]);
        Console.WriteLine("Type of the created instance was: {0}", loader.CreateInstance(assemblyName, types[create]));
    }

    string[] LoadAssembly(string path, out string assemblyName)
    {
        Console.WriteLine("LoadAssembly executing in appdomain {0}", AppDomain.CurrentDomain.FriendlyName);
        Assembly assembly = Assembly.Load(File.ReadAllBytes(path));
        assemblyName = assembly.FullName;
        return Array.ConvertAll<Type, string>(assembly.GetExportedTypes(), x => x.FullName);
    }

    string CreateInstance(string assemblyName, string typeName)
    {
        Console.WriteLine("CreateInstance executing in appdomain {0}", AppDomain.CurrentDomain.FriendlyName);
        object instance = Activator.CreateInstance(assemblyName, typeName).Unwrap();
        // do something useful with the instance here
        return instance.GetType().FullName;
    }

    public override object InitializeLifetimeService()
    {
        return null;
    }
}

Sample output for log4net.dll:

$ mono main.exe log4net.dll
LoadAssembly executing in appdomain other
Loaded assembly log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=a5715cc6d5c3540b.
Types:
[0] log4net.Core.SecurityContext
[1] log4net.Core.LoggerWrapperImpl
[2] log4net.Core.LogImpl
[3] log4net.Core.DefaultRepositorySelector
...
[160] log4net.Appender.AspNetTraceAppender
[161] log4net.Appender.FileAppender
...
[197] log4net.Repository.LoggerRepositoryConfigurationResetEventHandler
[198] log4net.Repository.LoggerRepositoryConfigurationChangedEventHandler
Enter index of type to create, -1 to exit: 161
Creating instance of type log4net.Appender.FileAppender
CreateInstance executing in appdomain other
Type of the created instance was: log4net.Appender.FileAppender