我的应用域名不会卸载

时间:2009-09-28 03:18:03

标签: c# appdomain

在运行时,我希望能够卸载DLL并重新加载它的修改版本。我的第一个实验陷入了火焰之中。谁能告诉我为什么?

private static void Main()
{
   const string fullPath = "C:\\Projects\\AppDomains\\distrib\\MyLibrary.dll";

   // Starting out with a  version of MyLibrary.dll which only has 1 method, named Foo()
   AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath);
   AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
   appDomain.Load(assemblyName);
   appDomain.DomainUnload += appDomain_DomainUnload;
   AppDomain.Unload(appDomain);

   // Breakpoint here; swap out different version of MyLibrary.dll which only has 1 method, named Goo()
   AssemblyName assemblyName2 = AssemblyName.GetAssemblyName(fullPath);
   AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
   Assembly asm2 = appDomain2.Load(assemblyName2);

   foreach (Type type in asm2.GetExportedTypes())
   {
      foreach (MemberInfo memberInfo in type.GetMembers())
      {
         string name = memberInfo.Name;
         // Breakpoint here: Found Foo and but no Goo! I was expecting Goo and no Foo.
      }
   }
}

private static void appDomain_DomainUnload(object sender, EventArgs e)
{
   // This gets called before the first breakpoint
}

编辑:

好的,这显然是我第一次发帖。感谢Daniel格式化我的代码(我现在看到工具栏按钮,以及预览窗格!)。我没有办法在回复那些要求澄清原始帖子或1个答案的人时发表“评论”的方式,所以我只是发布另一个“答案”来保持对话的进行。 (对于如何完成评论表示赞赏)。

对帖子的评论: Mitch - 陷入火焰之中'因为我的foreach循环应该迭代修改后的DLL中的类型,而不是先前加载/卸载的类型。 马修 - 这可能会奏效,但我确实需要使用相同文件名的情况。 迈克二 - 没有强烈的名字。

对答案的评论: 蓝& Mike Two - 我会提出你的建议,但首先我需要了解一个关键方面。我已经读过你必须注意不要将程序集拉入主应用程序域,并且代码以前在卸载之前有一个foreach循环的副本。因此,怀疑访问MethodInfos是将程序集吸入主应用程序域,我删除了循环。那时我知道我需要寻求帮助,因为第一个DLL 仍然不会卸载!

所以我的问题是:以下代码段中的内容会导致主app域直接(或间接)访问dll中的任何 ...为什么会导致程序集也加载在主应用领域:

appDomain.Load(assemblyName);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);

没有给我很大信心我在卸载它之前实际上能够使用它。


编辑2:

Mike Two - 感谢您的坚持......从DoCallBack中加载程序集是秘诀。对于任何感兴趣的人,这里有一些可能有用的信息:

听起来没有人能真正重现我的条件。为了演示原始问题,我以这种方式生成了我的dll:1。将类库项目添加到解决方案2.使用Foo构建版本1.0.0;将生成的程序集重命名为MyLibrary.dll.f。 3.将Foo重命名为Goo并构建另一个版本1.0.0;将生成的程序集重命名为MyLibrary.dll.g。 4.从解决方案中删除项目。在开始运行之前,我删除了.f并运行到断点(卸载后的第一行)。然后我重新打开.f并从另一个dll中移除.g并运行到下一个断点。 Windows并没有阻止我重命名。注意:虽然这是更好的练习,但我没有更改版本号,因为我不想假设我的客户总是这样,因为AssemblyInfo中的默认条目不是通配符版本。这似乎是一个丑陋的案件。

另外,我刚刚发现了一些可以早日让我了解的东西:

 AssemblyName assemblyName = AssemblyName.GetAssemblyName(FullPath);
 AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
 appDomain.Load(assemblyName);
 Assembly[] tempAssemblies = appDomain.GetAssemblies();
 // MyLibrary.dll has been loaded into the temp domain...good
 Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
 // MyLibrary.dll has been loaded into the main domain, too...bad!

所以我不确定AppDomain.Load是什么意思,但它似乎也有将加载程序集加载到主应用程序域的额外副作用。使用这个实验,我可以看到Mike Two的解决方案只是将它干净地加载到临时域中:

 AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
 appDomain.DoCallBack(CallBackDelegate);  // Executes Assembly.LoadFrom
 Assembly[] tempAssemblies = appDomain.GetAssemblies();
 // MyLibrary.dll has been loaded into the temp domain...good
 Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
 // MyLibrary.dll has NOT been loaded into the main domain...great!

那么,Mike Two,这个StackOverflow新手究竟是如何将你的回答标记为“已接受”的?或者我不能这样做,因为我只是这里的客人?

现在我要学习如何实际使用 MyLibrary而不将程序集吸入主应用程序域。谢谢大家的参与。


编辑3:

BlueMonkMN - 我继续前进并取消了活动订阅并获得了相同的结果。完整的程序现在列在下面:

 const string fullPath = "C:\\Projects\\AppDomains\\distrib\\MyLibrary.dll";

 // Starting out with a  version of MyLibrary.dll which only has 1 method, named Foo()
 AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath);
 AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
 appDomain.Load(assemblyName);
 AppDomain.Unload(appDomain);

 // Breakpoint here; swap out different version of MyLibrary.dll which only has 1 method, named Goo()
 AssemblyName assemblyName2 = AssemblyName.GetAssemblyName(fullPath);
 AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
 Assembly asm2 = appDomain2.Load(assemblyName2);

 foreach (Type type in asm2.GetExportedTypes())
 {
    foreach (MemberInfo memberInfo in type.GetMembers())
    {
       string name = memberInfo.Name;
       // Breakpoint here: Found Foo and but no Goo! I was expecting Goo and no Foo.
    }
 }

似乎应该没有办法将程序集拉入这两行之间的主应用程序域:

appDomain.Load(assemblyName); 
AppDomain.Unload(appDomain);

2 个答案:

答案 0 :(得分:5)

我试图复制这个。在加载的DLL(MyLibrary.dll)中我构建了两个版本。第一个类有一个名为Foo的方法,版本号为1.0.0.0。第二个具有相同的类,但该方法已重命名为Bar(我是传统主义者),版本号为2.0.0.0。

我在卸载调用后放了一个断点。然后我尝试在第一个版本的顶部复制第二个版本。我认为这就是你正在做的事情,因为路径永远不会改变。 Windows不允许我将版本2复制到版本1上.dll已被锁定。

我使用DoCallback更改了代码以使用在AppDomain中执行的代码加载dll。那很有效。我可以交换dll并找到新方法。这是代码。

class Program
{
    static void Main(string[] args)
    {
        AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
        appDomain.DoCallBack(loadAssembly);
        appDomain.DomainUnload += appDomain_DomainUnload;

        AppDomain.Unload(appDomain);

        AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
        appDomain2.DoCallBack(loadAssembly);
    }

    private static void loadAssembly()
    {
        string fullPath = "LoadMe1.dll";
        var assembly = Assembly.LoadFrom(fullPath);
        foreach (Type type in assembly.GetExportedTypes())
        {
            foreach (MemberInfo memberInfo in type.GetMembers())
            {
                string name = memberInfo.Name;
                Console.Out.WriteLine("name = {0}", name);
            }
        }
    }

    private static void appDomain_DomainUnload(object sender, EventArgs e)
    {
        Console.Out.WriteLine("unloaded");
    }
}

我没有强烈命名这些集会。如果你这样做,你可能会发现第一个缓存。您可以通过从命令行运行gacutil / ldl(列表下载缓存)来判断。如果你确实发现它被缓存运行gacutil / cdl来清除下载缓存。

答案 1 :(得分:1)

我发现如果您直接访问程序集中的类型,它会被加载到您自己的域中。所以我必须做的是创建第三个程序集,实现两个程序集共有的接口。该程序集被加载到两个域中。然后,在与外部组件交互时,请注意仅使用第三个组件的接口。这应该允许您通过卸载其域来卸载第二个程序集。