在运行时,我希望能够卸载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为我的代码设置格式(我现在也看到了工具栏上的按钮和预览 Pane !)。对于那些要求澄清原始帖子或第一个答案的人,我看不到发布“评论”的方法,因此,我将发布另一个“答案”以保持对话的进行。 (赞赏如何评论的指针将不胜感激)。
发表评论:
Mitch-陷入困境,因为我的foreach循环应该一直在修改后的DLL中的类型上进行迭代,而不是以前加载/卸载的DLL中的类型。
马修(Matthew)-这可能有用,但是我确实需要使用相同文件名的大小写。
迈克二世(Mike Two)-没有强力名字
对答案的评论:
Blue&Mike Two-我会为您提供建议,但首先我需要了解一个关键方面。我读过,您必须注意不要将程序集拉入主应用程序域,并且该代码以前在卸载之前具有foreach循环的副本。因此,怀疑访问MethodInfos将程序集吸引到了主应用程序域中,因此删除了循环。那时我知道我需要寻求帮助,因为第一个DLL仍然无法卸载!
所以我的问题是:在以下代码段中,什么导致主应用程序域直接(或间接)访问dll中的任何内容……为什么会导致程序集也加载到主应用程序域中:
appDomain.Load(assemblyName);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);
并没有给我太大的信心,我在卸载DLL之前实际上已经能够使用它。

编辑2:
迈克二世(Mike Two)-感谢您的坚持……从DoCallBack内加载程序集是 secret 所在。对于任何有兴趣的人,以下是一些可能对以后有用的信息:
听起来没有人能真正准确地再现我的状况。为了演示原始问题,我以这种方式生成了我的dll:1.将类库项目添加到解决方案中2.使用Foo构建版本1.0.0;重命名结果程序集为MyLibrary.dll.f。 3.将Foo重命名为Goo,并构建了另一个1.0.0版本;重命名结果程序集为MyLibrary.dll.g。 4.从解决方案中删除了项目。在开始运行之前,我删除了.f并运行到断点(卸载后的第一行)。然后,我将.f重新固定,并将.g从另一个dll中删除,然后运行到下一个断点。 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的解决方案将其干净地仅加载到temp域中:
 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!
那么,迈克二号,这个StackOverflow新手究竟如何将您的答案标记为“已接受”?还是因为我只是这里的客人而不能这样做吗?
现在,我要学习如何实际使用MyLibrary而不将程序集引入主应用程序域中。感谢大家的参与。

编辑3:
BlueMonkMN-我继续进行并取消了 Activity 订阅并获得了相同的结果。现在,完整的程序列在下面:
 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);

最佳答案

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

10-07 19:57
查看更多