23
votes

I need to execute a method in an assembly loaded during runtime. Now I want to unload those loaded assemblies after the method call. I know that I need a new AppDomain so I can unload the libraries. But here, the problem arises.

The assemblies going to load are plugins in my plugin framework. They have no entry point at all. All I know is that they contain some types which implement a given interface. The old, non-AppDomain-code looks like this (slightly shortened):

try
{
    string path = Path.GetFullPath("C:\library.dll");
    AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    Assembly asm = Assembly.LoadFrom(path);
    Type[] types = asm.GetExportedTypes();
    foreach (Type t in types)
    {
        if ((t.GetInterface("IStarter") != null) && !t.IsAbstract)
        {
            object tempObj = Activator.CreateInstance(t);
            MethodInfo info = t.GetMethod("GetParameters");
            if (info != null)
            {
                return info.Invoke(tempObj, null) as string;
            }
        }
    }
}
catch (Exception ex)
{
    MessageBox.Show(String.Format("Damn '{0}'.", ex.Message), "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    if (args.Name.StartsWith("MyProject.View,"))
    {
        string path = Path.GetFullPath("C:\view.dll"));
        return Assembly.LoadFrom(path);
    }
    return null;
}

Now I want them to load in an own AppDomain:

try
{
    string path = Path.GetFullPath("C:\library.dll");
    AppDomain domain = AppDomain.CreateDomain("TempDomain");
    domain.AssemblyResolve += CurrentDomain_AssemblyResolve;  // 1. Exception here!!
    domain.ExecuteAssembly(path);  // 2. Exception here!!
    domain.CreateInstanceFrom(...);  // 3. I have NO clue, how the type is named.
    domain.Load(...);  // 4. I have NO clue, how the assembly is named.
    domain.DoCallBack(...); // 5. Exception here!!
    // ...
}
catch (Exception ex)
{
    MessageBox.Show(String.Format("Damn '{0}'.", ex.Message), "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

As you can see, I have put in 5 cases.

  1. If I set the event handler, I get an exception that the assembly (it's an management console (mmc.exe) SnapIn. could not be found/loaded.

  2. ExecuteAssembly does not find an entry point (well, there is none).

  3. I have no clue how the type is named. How to load by interface?

  4. Similar to 3. How to get the name of an assembly?

  5. Same error as in 1.

I think the problem could be the managment console somehow or I have just no clue what I'm doing wrong. Any help is appreciated.

UPDATE 1

I have now tried using the posted proxy-solution.

AppDomain domain = AppDomain.CreateDomain("TempDomain");
InstanceProxy proxy = domain.CreateInstanceAndUnwrap(Assembly.GetAssembly(
    typeof(InstanceProxy)).FullName, typeof(InstanceProxy).ToString()) as InstanceProxy;
if (proxy != null)
{
    proxy.LoadAssembly(path);
}
AppDomain.Unload(domain);

public class InstanceProxy : MarshalByRefObject
{
    public void LoadAssembly(string path)
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
        Assembly asm = Assembly.LoadFrom(path);
        Type[] types = asm.GetExportedTypes();
        // ...see above...
    }
}

This does not work either. When trying to create the proxy object, I get an exception:

Could not load file or assembly 'MyProject.SnapIn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

The file in the error message is the on loaded into mmc (the SnapIn). Any idea how to fix this error? AppDomain.AssemblyResolve is not called (neither in the old or new domain).

UPDATE 2

I have now tried the solution with the AppDomainSetup. Now, the exception has changed to:

Could not load file or assembly 'file:///C:/Development/MyProject/bin/SnapIn/MyProject.SnapIn.DLL' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)

Any idea?

3
Did you tried to create a class which inherits from MarshallByRef to act as proxy and let it do that dirty work in new application domain context?Rubens Farias
Would this class be in the assembly to load or in the already loaded assembly (the one trying to load the others)?Scoregraphic
Don't use CreateInstanceAndUnwrap, use CreateInstanceFromAndUnwrap.BFree
Ah, that did the trick. Thanks lot.Scoregraphic
Hi, This is from architecture perspective. Could you please detail the scenario where you are using this approach of creating a new app domain and then unloading after calling a few members? Thanks!thewpfguy

3 Answers

15
votes

Try this:

namespace SeperateAppDomainTest
{
    class Program
    {
        static void Main(string[] args)
        {
            LoadAssembly();
        }

        public static void LoadAssembly()
        {
            string pathToDll = Assembly.GetExecutingAssembly().CodeBase;
            AppDomainSetup domainSetup = new AppDomainSetup { PrivateBinPath = pathToDll };
            var newDomain = AppDomain.CreateDomain("FooBar", null, domainSetup);
            ProxyClass c = (ProxyClass)(newDomain.CreateInstanceFromAndUnwrap(pathToDll, typeof(ProxyClass).FullName));
            Console.WriteLine(c == null);

            Console.ReadKey(true);
        }
    }

    public class ProxyClass : MarshalByRefObject { }
1
votes

Take a look into this previous answer: How to load an assembly into different AppDomain on Windows Mobile (.NET CF) ?. That answer creates a proxy class which runs into new AppDomain context so, there, you can have full control of your initialization.

You could to create a Start() method into ServiceApplicationProxy class and just call it normally from your hoster with a proxy.Start().

0
votes

https://msdn.microsoft.com/en-us/library/3c4f1xde%28v=vs.110%29.aspx

specifies that

typeName Type: System.String

The fully qualified name of the requested type, including the namespace but not the assembly, as returned by the Type.FullName

property.

So try calling with Fully qualified name, instead of using typeof(InstanceProxy).ToString() use string/text "<<Namespace>>.InstanceProxy"

as below

InstanceProxy proxy = domain.CreateInstanceAndUnwrap(path, "<<Namespace>>.InstanceProxy") as InstanceProxy;