.NET与原生COM对象之间的连接点问题解决方案

在.NET与原生COM对象交互时,可能会遇到一些棘手的问题,如挂起问题。本文将介绍这类问题产生的原因,并提供相应的解决方案。

问题概述

挂起问题通常是由于垃圾回收器未能释放原生COM接口(RCW),而原生COM对象却在等待所有引用被释放。Paresh Suthar的博客提供了一种解决方案,即在.NET代码中调用Marshal.ReleaseComObject来释放RCW,从而避免问题。但若无法修改.NET代码,则可以尝试调用CoEEShutDownCom来释放所有RCW引用。Adam Nathan在其著作《.NET与COM:完全互操作性指南》中提到了这种可能性,但没有给出示例。特别是在.NET 4.0中,全局函数CoEEShutDownCom已被弃用,需要一些额外的工作来实现解决方案。

RCW(Runtime Callable Wrapper)是.NET为原生COM对象提供的包装器。在本文的案例中,RCW对象是原生接收器对象的包装器。

问题重现

如果有一个MFC应用程序(作为COM客户端)调用一个用C#实现的COM服务器,并且COM服务器支持连接点,通过C#属性ComSourceInterfaces,可以在退出时重现这个问题。Paresh Suthar在其博客中很好地解释了使用ComSourceInterfaces属性时会发生什么。当原生应用程序是MFC时,问题变得更加严重。应用程序在尝试退出时可能会挂起。(实际上,如果制造一个低内存事件,这将导致垃圾回收操作,应用程序将退出。但制造低内存事件并不是一个稳定的工作。)

退出挂起是由MFC框架的AfxOleCanExitApp()检查引起的。在MFC世界中,通常在COM对象的构造函数中调用AfxOleLockApp,在析构函数中调用AfxOleUnlockApp。在案例中,AfxOleUnlockApp没有被调用,因为接收器对象没有被释放,接收器对象的最新引用在RCW中。

解决方案

在实际案例中,无法采纳Paresh Suthar的建议去修改.NET代码(它们是客户的源代码),只能修改原生接收器对象。经过深入的Google搜索和阅读代码,相信Adam Nathan的建议,使用CoEEShutDownCom来释放所有RCW的引用应该是解决方案。

"调用CoEEShutDownCOM会强制CLR释放它在RCWs中持有的所有接口指针。通常不需要调用此方法,但如果需要在进程终止前释放所有接口指针,或者运行泄漏检测代码,可能需要调用它。"

然后,查看了MSDN,全局CoEEShutDownCOM已被弃用。需要使用以下代码在原生世界中实现一个全局函数。

void CoEEShutdownCOM() { ULONG aFetched = 1; ICLRMetaHost *meta = NULL; ICLRRuntimeInfo *info = NULL; HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&meta); IEnumUnknown * pRtEnum = NULL; DWORD Aid = 0; ULONGLONG bytes = 0; ULONG fetched = 0; hr = meta->EnumerateLoadedRuntimes(GetCurrentProcess(), &pRtEnum); while((hr = pRtEnum->Next(1, (IUnknown **)&info, &fetched)) == S_OK && fetched > 0) { void (*pfunc)(); info->GetProcAddress("CoEEShutDownCOM", (void**)&pfunc); if(pfunc) { pfunc(); } info->Release(); } pRtEnum->Release(); meta->Release(); }

AfxOleCanExitApp()检查之前调用这个函数,世界得救了。

讨论

即使问题可以通过这个函数解决,但并不完美。在退出时,强制释放所有RCW对象引用。也许,一些引用仍然需要。在应用这个解决方案之前,需要考虑可能性。

在想,如果有一种方法(在原生世界中)来释放一个特定的RCW对象,知道它是接收器的包装器,那将是完美的解决方案。但没有找到任何解决方案,即使它是可能的还是不可能。欢迎提供对此方法的评论。或者任何其他建议?

在本文中,提供了重现由连接点技术引起的挂起问题的示例代码。并提供了解决方案代码。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485