在运行时,我希望能够卸载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);
没有给我很大信心我在卸载它之前实际上能够使用它。
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而不将程序集吸入主应用程序域。谢谢大家的参与。
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);
答案 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)
我发现如果您直接访问程序集中的类型,它会被加载到您自己的域中。所以我必须做的是创建第三个程序集,实现两个程序集共有的接口。该程序集被加载到两个域中。然后,在与外部组件交互时,请注意仅使用第三个组件的接口。这应该允许您通过卸载其域来卸载第二个程序集。