COM互操作是否尊重.NET AppDomain边界进行程序集加载?

9
这是核心问题:我有一个.NET应用程序,在单独的AppDomain中使用COM互操作。COM部分似乎将程序集加载回默认域,而不是从调用COM部分的AppDomain中加载。
我想知道的是:这是预期行为还是我做错了什么,导致这些与COM相关的程序集在错误的AppDomain中加载?请参见下面更详细的情况描述...
该应用程序由3个程序集组成: - 主EXE,应用程序的入口点。 - common.dll,仅包含IController接口(以IPlugin风格) - controller.dll,包含实现IController和MarshalByRefObject的Controller类。此类执行所有工作并使用COM互操作与另一个应用程序交互。
主EXE的相关部分如下:
AppDomain controller_domain = AppDomain.CreateDomain("Controller Domain");
IController c = (IController)controller_domain.CreateInstanceFromAndUnwrap("controller.dll", "MyNamespace.Controller");
result = c.Run();
AppDomain.Unload(controller_domain);

common.dll只包含这两个东西:

public enum ControllerRunResult{FatalError, Finished, NonFatalError, NotRun}
public interface IController
{
    ControllerRunResult Run();
}

控制器.dll包含这个类(也调用COM互操作的内容):

public class Controller: IController, MarshalByRefObject

当第一次运行应用程序时,Assembly.GetAssemblies()看起来正常,common.dll被加载到两个AppDomains中,而controller.dll仅被加载到控制器域中。然而,在调用c.Run()之后,我发现与COM互操作相关的程序集已被加载到默认的AppDomain中,而不是从进行COM互操作的AppDomain中加载。
为什么会发生这种情况?
如果您感兴趣,这里是一些背景信息:
最初,这是一个单一的AppDomain应用程序。它所接口的COM内容是一个不稳定的服务器API。当来自COM内容的COMException(没有有用的诊断信息)发生时,整个应用程序必须重新启动才能再次使用COM连接。简单地重新连接到COM应用程序服务器会立即导致COM异常。为了应对这种情况,我尝试将COM互操作内容移入一个单独的AppDomain中,以便在发生神秘的COMExceptions时,可以卸载其中发生的AppDomain,创建一个新的并重新开始,所有这些都无需手动重新启动应用程序。那是理论上的想法...

顺便提一句,如果其他人也看到了这个问题 - 这是由于HP Quality Center API DLLs导致的。我通过让应用程序重新启动来解决了这个问题,但我仍然很想知道为什么会发生这种情况。 - Xiaofu
3个回答

17

很遗憾,COM组件在进程空间中加载而不是在AppDomain上下文中加载。因此,您需要手动撤销(释放和卸载)本机DLL(适用于COM和P/Invoke)。简单地销毁AppDomain没有任何作用,但重新生成整个进程不应该是重置COM状态的必要条件(仅仅重新创建COM对象通常也应该工作,这似乎是组件提供商代码中的一个错误,也许他们可以解决它?)

参考资料

(TechNet) 进程地址空间

(MSDN) 应用程序域

(MSDN) 边界:进程和应用程序域


感谢您的回复,肖恩。现在已经过了这么长时间,我已经记不清是否尝试过重新创建COM对象,但我怀疑我已经尝试过了。有时间时,我会尝试您的建议。同时,感谢您提到了进程空间,给您点赞。 - Xiaofu
所以我还没有机会尝试这个,但你确实回答了主要问题,所以我会将其标记为已回答。非常感谢大家的参与。 - Xiaofu
是的,我确实怀疑这是提供商代码中的一个错误,特别是由于导致错误的原因让我尝试卸载/重新加载COM组件成了一个真正的谜团。不过在责怪别人之前,我应该再次检查我的代码。 - Xiaofu
谢谢,这很有帮助。有人能提供链接或其他参考资料,以便我更好地理解进程空间与应用程序域之间的区别,以及.NET作为COM如何使用进程空间吗? - Nate Sauber
“进程空间”是指进程的整个内存占用,其缩写为“进程地址空间”。 “应用程序域”是进程地址空间内为.NET应用程序保留的一小部分内存区域。 .NET应用程序可以分配多个应用程序域,但通常只有一个主应用程序域(称为“主应用程序域”)。COM和WinAPI DLL被加载到进程地址空间中分配给应用程序域之外的内存中,然后CLR将它们映射到每个已加载的应用程序域中,以便其中的.NET代码能够看到它们。 希望有所帮助。 - Shaun Wilson

3
这是Shaun Wilson答案正确的证明:

Com App Domain


1
不要让你的控制器成为MBR。创建一个小代理,在第二个域中加载控制器并启动它。这样,控制器dll就不会在第一个域中加载。

你好Sunny,使用IController接口在一个公共dll中的想法就是这样产生的。Controller.dll不会被加载到默认域中。控制器域中的Controller类所引起的COM互操作活动似乎会导致与COM相关的DLL被加载到默认域中。 - Xiaofu
更新:使用代理在另一个域中创建和运行控制器具有相同的效果。Controller.dll没有加载到默认的AppDomain中(那不是问题),但COM互操作仍然似乎将COM程序集加载回默认域中。 - Xiaofu
你是否尝试在第二个域中手动加载interop程序集,而不是依赖于自动加载器? - Sunny Milenov
你是在建议手动加载.NET互操作程序集,还是加载互操作程序集引发的COM程序集?我会尽快查看并检查发生的情况。 - Xiaofu
1
我不得不加载interop程序集。 - Sunny Milenov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接