在不同的AppDomain中加载/卸载程序集

24

我需要在运行时加载的程序集中执行一个方法。现在我想在方法调用后卸载已加载的程序集。我知道我需要一个新的AppDomain来卸载这些库。但是,问题出现了。

要加载的程序集是我的插件框架中的插件。它们根本没有入口点。所有我知道的是它们包含一些实现给定接口的类型。旧的、非AppDomain代码如下(略有缩短):

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;
}

现在我想让它们在自己的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);
}

如你所见,我已经提供了5种情况。
  1. 如果我设置事件处理程序,会出现找不到/加载程序集(它是管理控制台(mmc.exe)的SnapIn)的异常。

  2. ExecuteAssembly找不到入口点(实际上没有入口点)。

  3. 我不知道类型的名称。如何按接口加载?

  4. 与3类似。如何获取程序集的名称?

  5. 与1中的错误相同。

我认为问题可能与管理控制台有关,或者我根本不知道自己做错了什么。感谢任何帮助。

更新1

我现在尝试使用发布的代理解决方案。

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...
    }
}

这个也不行。在尝试创建代理对象时,我遇到了异常:
无法加载文件或程序集'MyProject.SnapIn, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'或其某一个依赖项。系统找不到指定的文件。
错误信息中提到的文件是加载到mmc中的文件(SnapIn)。有任何想法如何修复此错误吗?AppDomain.AssemblyResolve没有被调用(旧域和新域都没有被调用)。
更新2
现在我已经尝试了使用AppDomainSetup的解决方案。现在,异常信息已经变成了:
无法加载文件或程序集“file:///C:/Development/MyProject/bin/SnapIn/MyProject.SnapIn.DLL”或其某一个依赖项。给定的程序集名称或代码库无效。(来自HRESULT的异常:0x80131047)
有任何想法吗?

1
你是否尝试创建一个继承自MarshallByRef的类作为代理,并让它在新的应用程序域上下文中执行这些“脏活”? - Rubens Farias
4
请使用CreateInstanceFromAndUnwrap而不是CreateInstanceAndUnwrap。 - BFree
你好,这是从架构角度来看的。 您能详细说明一下在什么情况下使用创建新应用程序域的方法,然后在调用几个成员后卸载它的场景吗?谢谢! - thewpfguy
我们有一个插件主机和未定义数量的插件需要处理。这些插件必须在运行后卸载(只在特定事件上加载),以便可以更新它们而无需重新启动服务主机。 - Scoregraphic
你能否提供一个完整的可运行代码示例?我正在尝试做类似的事情,但无法使其工作。 - deadlydog
显示剩余3条评论
3个回答

16

试试这个:

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

谢谢你的回答。我已经尝试过了,但还是出现了错误。请查看我在原始问题中的更新。 - Scoregraphic

0

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

指定

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.

所以尝试使用完全限定名称进行调用,而不是使用 typeof(InstanceProxy).ToString() 而是使用字符串/文本 "<<Namespace>>.InstanceProxy"

如下所示

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

typeof(InstanceProxy).FullName 比硬编码命名空间和类型名称更好,不是吗? - Maarten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接