C# 我的应用程序域赢了';不卸货

C# 我的应用程序域赢了';不卸货,c#,appdomain,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 F

在运行时,我希望能够卸载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中迭代类型,而不是之前加载/卸载的类型,所以崩溃了。 Matthew-这可能有用,但我真的需要使用相同文件名的情况。 迈克二号-名字不太清楚

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

因此,我的问题是:以下代码段中的哪些内容会导致主应用程序域直接(或间接)访问dll中的任何内容…为什么会导致程序集也加载到主应用程序域中:

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!
那么,迈克二号,这个新手到底是如何把你的答案标记为“接受”的呢?或者我不能这么做,因为我只是这里的客人

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

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

我尝试复制此程序集。在要加载的dll(MyLibrary.dll)中,我构建了两个版本。第一个类有一个名为Foo的类和一个方法,版本号为1.0.0.0。第二个有相同的类,但是方法被重命名为Bar(我是一个传统主义者),版本号为2.0.0.0

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

我更改了代码,使用在AppDomain中使用DoCallback执行的代码加载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以清除下载缓存。

您能解释一下“着火了”吗?我会检查您是否真的看到了第二个程序集。将它们作为两个不同的文件名并排尝试,而不是使它们具有相同的名称。程序集是否具有强名称?GAC有一个程序集下载缓存,用于保存强名称程序集。您可以通过运行gacutil/ldl来检查它是否在那里,并通过运行gacutil/cdl来清除它。@JimC这可能需要2.5年的时间,但您最终可以接受关于这个问题的答案(正如您很久以前表示的那样),如果您愿意=),另一种避免将类型拉入主域的方法是使用AppDomain.DoCallBack。这允许您在加载的域中执行代码。在OP的示例中,foreach。。。无法传递到加载的AppDomain。