我的应用程序域无法卸载
在运行时,我希望能够卸载 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 格式化我的代码(我现在看到了执行此操作的工具栏按钮,以及预览窗格!)。我看不到如何发布“评论”来回复那些要求对原始帖子或第一个答案进行澄清的人,因此我将发布另一个“答案”以保持对话继续进行。 (关于如何完成评论的指示将不胜感激)。
对帖子的评论: Mitch - 火了,因为我的 foreach 循环应该迭代修改后的 DLL 中的类型,而不是之前加载/卸载的类型。 马修 - 这可能有用,但我确实需要相同文件名的大小写才能工作。 迈克二号 - 名字不强。
对答案的评论: 蓝色&迈克二号 - 我会采纳你的建议,但首先我需要了解一个关键方面。我读过,您必须注意不要将程序集拉入主应用程序域,并且代码在卸载之前之前有一个 foreach 循环的副本。因此,我怀疑访问 MethodInfos 会将程序集吸入主应用程序域,因此删除了循环。就在那时我知道我需要寻求帮助,因为第一个 DLL仍然无法卸载!
所以我的问题是:以下代码段中的内容导致主应用程序域直接(或间接)访问 dll 中的任何内容...为什么它会导致程序集也加载到主应用程序域中应用程序域:
appDomain.Load(assemblyName);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);
在卸载 DLL 之前,我对自己是否能够真正使用该 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,这个 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);
At runtime, I'd like to be able to unload a DLL and reload a modified version of it. My first experiment went down in flames. Can anyone tell me why?
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
}
Edit:
Okay, this is obviously my first time posting. Thanks Daniel for formatting my code (I now see the toolbar button to do that, and the preview pane, too!). I don't see a way to post a "comment" in reply to either those who asked for clarification to the original post or to the 1 answer, so I'll just post another "answer" to keep the conversation going. (Pointers appreciated on how comments are done would be appreciated).
Comments to the posting:
Mitch - Went down in flames 'cause my foreach loop should have been iterating over the types in the modified DLL, not the previously loaded/unloaded one.
Matthew - That might work, but I really need the case of the same file name to work.
Mike Two - Not strongly named.
Comments to the answer:
Blue & Mike Two - I'll noodle on your suggestions, but first I need to understand a critical aspect. I had read that you have to take care not to pull the assembly into the main app domain, and the code previously had a copy of the foreach loop before the unload. So, suspecting that accessing MethodInfos was sucking the assembly into the main app domain, I removed the loop. That's when I knew I needed to ask for help, 'cause the first DLL still wouldn't unload!
So my question is: What in the following code segment causes the main app domain to ever directly (or indirectly) access anything in the dll...why would it cause the assembly to also load in the main app domain:
appDomain.Load(assemblyName);
appDomain.DomainUnload += appDomain_DomainUnload;
AppDomain.Unload(appDomain);
Didn't give me much confidence I'd ever actually be able to use the DLL before unloading it.
Edit 2:
Mike Two - Thanks for your persistence...loading the assembly from within DoCallBack was the secret sauce. For anyone interested, here is some more info that might be useful down the road:
Sounds like no one could actually reproduce my conditions exactly. To demonstrate the original problem, I generated my dlls this way: 1. Added class library project to solution 2. Built version 1.0.0 with Foo; renamed resulting assembly as MyLibrary.dll.f. 3. Renamed Foo to Goo and built another version 1.0.0; renamed resulting assembly as MyLibrary.dll.g. 4. Removed project from solution. Before starting the run, I removed the .f and ran to breakpoint (first line after the unload). Then I tacked the .f back on and removed the .g from the other dll and ran to the next breakpoint. Windows didn't stop me from renaming. Note: Although it would be better practice, I didn't change the version number because I didn't want to assume my customers always would, since the default entry in AssemblyInfo isn't the wildcard version. It seemed like the uglier case to handle.
Also, I just now discovered something that would have clued me in sooner:
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!
So I'm not sure what the point of AppDomain.Load is, but it seems to have the bonus side effect of loading the assembly into the main app domain as well. Using this experiment, I could see Mike Two's solution cleanly loads it only into the temp domain:
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!
So, Mike Two, exactly how does this StackOverflow newbie mark your answer as "accepted"? Or can I not do that since I'm only a guest here?
Now I'm off to learn how to actually use MyLibrary without sucking the assembly into the main app domain. Thanks everyone for participating.
Edit 3:
BlueMonkMN - I went ahead and took out the event subscription and get the same result. The full program is now listed below:
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.
}
}
Seems like there should be no way the assembly is getting pulled into the main app domain between these 2 lines:
appDomain.Load(assemblyName);
AppDomain.Unload(appDomain);
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我试图复制这个。在要加载的dll(MyLibrary.dll)中我构建了两个版本。第一个类有一个类,其中有一个名为 Foo 的方法,版本号为 1.0.0.0。第二个具有相同的类,但方法已重命名为 Bar(我是传统主义者),版本号为 2.0.0.0。
我在卸载调用后放置了一个断点。然后我尝试在第一个版本的基础上复制第二个版本。我认为这就是您正在做的事情,因为路径永远不会改变。 Windows 不允许我在版本 1 上复制版本 2。该 dll 被锁定。
我更改了代码以使用 DoCallback 在 AppDomain 内执行的代码来加载 dll。那行得通。我可以交换 dll 并找到新方法。这是代码。
我没有对程序集强命名。如果这样做,您可能会发现第一个已缓存。您可以通过从命令行运行 gacutil /ldl(列出下载缓存)来判断。如果您确实发现它已缓存,请运行 gacutil /cdl 来清除下载缓存。
I tried to replicate this. In the dll to load (MyLibrary.dll) I built two versions. The first had one class with one method named Foo and had a version number of 1.0.0.0. The second had the same class but the method had been renamed Bar (I'm a traditionalist) and a version number of 2.0.0.0.
I put a breakpoint after the unload call. Then I tried to copy the second version on top of the first version. I assume that is what you are doing because the path never changes. Windows would not let me copy version 2 over version 1. The dll was locked.
I changed the code to load the dll using code executed inside the AppDomain by using DoCallback. That worked. I could swap the dll's and find the new method. Here is the code.
I did not strong name the assemblies. If you do you are likely to find the first one cached. You can tell by running gacutil /ldl (List download cache) from the command line. If you do find it cached run gacutil /cdl to clear the download cache.
我发现,如果您直接访问程序集中的类型,它就会加载到您自己的域中。因此,我必须做的是创建第三个程序集,该程序集实现两个程序集通用的接口。该程序集被加载到两个域中。然后,在与外部程序集交互时,请注意仅使用第三个程序集的接口。这应该允许您通过卸载其域来卸载第二个程序集。
I have found that if you ever directly access types in an assembly it gets loaded into your own domain. So what I have had to do is create a third assembly that implements interfaces common to both assemblies. That assembly gets loaded into both domains. Then just be careful to only use interfaces from that third assembly when interacting with the external assembly. That should allow you to unload the second assembly by unloading its domain.