> 文章列表 > COM 对象析构函数是非常敏感的函数

COM 对象析构函数是非常敏感的函数

COM 对象析构函数是非常敏感的函数

如果你试图在 COM 对象的析构函数中做太多事情,你会发现自己有麻烦。

此话怎讲?

举个例子,如果析构函数将自身引用交给其他函数,则这些函数可能会决定调用 IUnknown::AddRef 和 IUnknown::Release 方法作为其内部操作的一部分。考察下面的代码:

>> 请移步至 topomel.com 以查看图片 <<

现在看起来不那么可怕了吧?对象在销毁时会调用 Save 保存自身。

但是,Save 方法可能会执行以下操作:

>> 请移步至 topomel.com 以查看图片 <<

就其本身而言,上面的代码看起来很正常。获取 IStream 并保存到它,将自己设置为其站点,以防 IStream 想要获取有关对象的其他信息作为保存过程的一部分。

但是,结合我们从析构函数中调用它的事实,这可能是一个灾难。观察释放最后一个引用时会发生什么情况。
> Release 方法将引用计数递减为零并执行删除此操作。
> 析构函数尝试保存对象。
> Save 方法获取存储流并将自身设置为站点。这会将引用计数从 0 增加到 1。
> SaveToStream 方法保存对象。
> Save 方法清除流上的站点。这会将引用计数从 1 减回零。
> 因此,Release 方法尝试第二次析构对象。

重复调用析构函数,可能往往会导致混乱。如果你幸运的话,你会在递归破坏中崩溃并识别来源,但如果你不幸运,由此产生的堆损坏在相当长的一段时间内不会被检测到,此时你只会挠头。

因此,至少应在 AddRef 方法中断言引用计数不会从零递增,如下图所示:

>> 请移步至 topomel.com 以查看图片 <<

这可以帮助我们尽早地发现重复调用析构函数的情况,让你有机会发现问题。但是,一旦你发现了问题,你能做些什么呢?
我们下次会研究这个问题。

总结

COM 对象基于引用计数的方式来管理对象的生存周期,这也导致了各种潜在的内存泄露问题,因为每一次的 AddRef,都必须有一个,且仅有一个 Release 与之伴随。
如果两者调用次数不匹配,则可能会出乱子。更糟糕的是,可能是程序运行了很久,在远离案发现场的地方出乱子。
这个时候如果想调试问题,就不大容易了。所以:请善用智能指针。

最后

Raymond Chen的《The Old New Thing》是我非常喜欢的博客之一,里面有很多关于Windows的小知识,对于广大Windows平台开发者来说,确实十分有帮助。
本文来自:《COM object destructors are very sensitive functions》