“不要在COM对象中使用两个点”是一个很好的经验法则,以避免COM引用泄漏,但Excel PIA可能会以比表面上更多的方式导致泄漏。
其中一种方式是订阅任何Excel对象模型的COM对象公开的事件。
例如,订阅Application类的WorkbookOpen事件。
关于COM事件的一些理论
COM类通过回调接口公开一组事件。为了订阅事件,客户端代码可以简单地注册实现回调接口的对象,COM类将响应特定事件并调用其方法。由于回调接口是COM接口,因此实现对象有责任对其接收到的任何COM对象(作为参数)的引用计数进行减少,以用于任何事件处理程序。
Excel PIA如何公开COM事件
Excel PIA将Excel Application类的COM事件公开为常规的.NET事件。每当客户端代码订阅.NET事件(重点在于'a')时,PIA将创建实现回调接口的类的实例,并向Excel注册它。
因此,针对不同的.NET代码订阅请求,会有多个回调对象向Excel注册。每个事件订阅对应一个回调对象。
事件处理的回调接口意味着PIA必须订阅每个.NET事件订阅请求的所有接口事件,不能挑选感兴趣的。当接收到事件回调时,回调对象检查关联的.NET事件处理程序是否对当前事件感兴趣,然后调用处理程序或者静默忽略回调。
对COM实例引用计数的影响
所有这些回调对象不会减少它们接收到的任何COM对象(作为参数)的引用计数,即使是那些被静默忽略的回调方法。它们完全依赖于
CLR垃圾回收器来释放COM对象。
由于GC运行是非确定性的,这可能会导致Excel进程持续时间比预期更长,并创建“内存泄漏”的印象。
解决方案
目前唯一的解决方案是避免使用PIA的COM类事件提供程序,编写自己的事件提供程序以确定性地释放COM对象。
对于Application类,可以通过实现AppEvents接口并使用
IConnectionPointContainer interface将其注册到Excel中来完成。Application类(以及所有使用回调机制公开事件的COM对象)都实现了IConnectionPointContainer接口。