C#如何从内存中正确卸载类?

时间:2012-07-05 19:13:35

标签: c# dynamic loading .net-assembly

我正在开发一种解决方案,其中服务在后台持续运行,并且可以在运行时添加/删除插件DLL。该服务将在需要时加载必要的插件,运行它们并卸载它们。这是卸载部分是目前给我带来的麻烦:一旦某个类第一次成功加载(变量tc),它就永远不会重新加载,即使DLL文件已更新。我认为我没有正确地卸载课程/程序集/ appdomain,所以我很欣赏一些最后一英里的建议。

编辑:我更新了帖子以反映最近的代码更改并解释了何时卸载无效:问题没有出现在Linux Ubuntu上(通过Mono),但它出现在Windows 2008 Server上,当我我试图用更新的文件版本替换某个插件DLL。似乎.NET框架已缓存到程序集的某个地方,并且在不重新加载的情况下很高兴。 DLL文件名不会改变,但文件版本属性不同,所以我希望运行时将先前加载的DLL版本与正在加载的版本进行比较,如果版本号不同,则使用较新版本。如果我稍微更改代码以从具有不同名称的DLL文件加载程序集,则重新加载按预期进行。

using System;
using System.Reflection;

namespace TestMonoConsole
{
    public interface ITestClass
    {
        void Talk();
    }
    class MainClass
    {
        public static void Main (string[] args)
        {
            string pluginPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

            string classAssembly = "TestClass";
            string className = "TestMonoConsole.TestClass";
            string command = "";
            do
            {
                try
                {
                    System.AppDomain domain = System.AppDomain.CreateDomain(classAssembly);
                    string pluginAssemblyFile = pluginPath + "/" + classAssembly + ".dll";
                    System.IO.StreamReader reader = new System.IO.StreamReader(pluginAssemblyFile, System.Text.Encoding.GetEncoding(1252), false);
                    byte[] b = new byte[reader.BaseStream.Length];
                    reader.BaseStream.Read(b, 0, System.Convert.ToInt32(reader.BaseStream.Length));
                    domain.Load(b);
                    reader.Close();
                    ITestClass tc = (ITestClass) Activator.CreateInstance(domain, classAssembly, className).Unwrap();
                    tc.Talk();
                    System.AppDomain.Unload(domain);
                }
                catch (System.IO.FileNotFoundException e)
                {
                    Console.WriteLine (String.Format("Error loading plugin: assembly {0} not found", classAssembly));
                }
                command = Console.ReadLine();
            } while (command == "");
        }
    }
}

5 个答案:

答案 0 :(得分:2)

您正在直接在主机域中创建类型。每个http://msdn.microsoft.com/en-us/library/ms224132(v=vs.90).aspx

使用Activator.CreateInstance时需要指定域

答案 1 :(得分:0)

此外,您可以使用托管加载项框架(MAF,System.AddIn)为您执行此操作,而不是重新发明此机制。查看here快速入门。

答案 2 :(得分:0)

无法从AppDomain卸载类型和程序集。 因此,您需要创建新的AppDomain,加载类型,执行所有操作,然后卸载该域。

答案 3 :(得分:0)

您正在自己的AppDomain中实例化插件类型。在多次实现插件框架之后,我强烈建议使用MEF(托管扩展性框架)作为解决方案,因为它处理代码隔离的这些常见问题。如果你不想要MEF,一种方法是实现一个“远程控制”类,它将充当app域之间的通信器。您可以在远程类上调用一个方法,该方法将在辅助应用程序域中实例化并运行代码。

MEF Documentation

答案 4 :(得分:0)

根据this MSDN article,CLR默认不使用卷影复制。您可以尝试按照以下步骤启用它:

  1. 创建AppDomainSetup
  2. 在AppDomainSetup实例中,将ShadowCopyFiles设置为true。
  3. 在AppDomainSetup实例中,将ShadowCopyFilesDirectories设置为包含您希望在运行时能够覆盖的程序集的目录路径。这可能只是插件程序集所在的目录。
  4. 在AppDomainSetup实例中,根据您的需要设置其他所有内容。
  5. 使用AppDomain.CreateAppDomain之一以AppDomainSetup为参数的重载。
  6. 尝试更换插件组件。
  7. 它适用于Mono但不适用于CLR(Windows)这一事实可能意味着CLI未指定是否应默认启用卷影复制。