我的应用程序域无法卸载

Posted

技术标签:

【中文标题】我的应用程序域无法卸载【英文标题】:My app domain won't unload 【发布时间】:2009-09-28 03:18:02 【问题描述】:

在运行时,我希望能够卸载 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 答案的人,所以我只会发布另一个“答案”以保持对话继续进行。 (感谢有关如何完成 cmets 的指针)。

对帖子的评论: Mitch - 着火了,因为我的 foreach 循环应该遍历修改后的 DLL 中的类型,而不是之前加载/卸载的类型。 马修 - 这可能有效,但我真的需要相同文件名的情况才能工作。 迈克二号 - 没有明确的名字。

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

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

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

在卸载 DLL 之前,我并没有给我太多信心。


编辑 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,这个 *** 新手究竟如何将您的答案标记为“已接受”?或者我只是这里的客人,我不能这样做吗?

现在我要学习如何在不将程序集吸入主应用程序域的情况下实际使用 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);

【问题讨论】:

你能解释一下“火上浇油”吗? 我会检查你是否真的在看第二个程序集。将它们作为两个不同的文件名并排尝试,而不是使它们具有相同的名称。 程序集是强命名的吗? GAC 具有保存强命名程序集的程序集下载缓存。您可以通过运行 gacutil /ldl 检查它是否在其中,并通过运行 gacutil /cdl 将其清除。 @JimC 这可能需要 2.5 年,但如果你愿意,你终于可以接受这个答案(正如你很久以前表示的那样)=) 【参考方案1】:

我试图复制这个。在要加载的 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 以清除下载缓存。

【讨论】:

【参考方案2】:

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

【讨论】:

另一种避免将类型拉入主域的方法是使用 AppDomain.DoCallBack。这允许您在加载的域中执行代码。在 OP 的示例中,foreach ... 可以传递给加载的 AppDomain。

以上是关于我的应用程序域无法卸载的主要内容,如果未能解决你的问题,请参考以下文章

Wix卸载快捷方式无法正常工作

我试图卸载应用程序域,但我的应用程序停止工作

无法卸载 chardet

无法通过 npm 全局卸载 typescript

MSI无法卸载

我搞砸了,我该如何卸载我的程序?