Loading/Unloading assembly in different AppDomain
Asked Answered
T

3

24

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?

Triangulate answered 25/1, 2010 at 13:45 Comment(8)
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?Unchristian
Would this class be in the assembly to load or in the already loaded assembly (the one trying to load the others)?Triangulate
Don't use CreateInstanceAndUnwrap, use CreateInstanceFromAndUnwrap.Coxcombry
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!Astonied
We have a plugin host and an undefined number of addins to work with. The addins must be unloaded after running (only loaded on certain events) so they can be updated without needing to restart the service host.Triangulate
Could you please post a full working code example? I'm trying to do something similar, but cannot get it to work.Donal
As of deadlydog's request, I uploaded an fully working example project to tooldesign.ch/pub/AppDomainTester.zipTriangulate
Please read my blog on cross-AppDomain Communication blog.vcillusion.co.in/…Footpoundsecond
C
16

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 { }
Coxcombry answered 25/1, 2010 at 14:35 Comment(0)
U
1

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().

Unchristian answered 25/1, 2010 at 13:59 Comment(1)
Thanks for your answer. I've tried this, but I'm still getting errors. See my update in the original question.Triangulate
K
0

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;
Koppel answered 24/11, 2016 at 14:28 Comment(1)
Wouldn't typeof(InstanceProxy).FullName be better than hardcoding the namespace + typename?Drawing

© 2022 - 2024 — McMap. All rights reserved.