如何在C ++中维护COM对象的弱引用?

时间:2011-05-25 07:19:28

标签: c++ windows com weak-references

在我的应用程序中,我正在挂钩创建COM对象的各种函数(例如CoCreateInstanceEx),以便在创建某个对象时得到通知。我正在跟踪std::list中所有创建的对象,我正在迭代该列表以执行各种操作(例如检查哪些OLE对象已被激活)。

现在的问题是,每当向我的列表添加IUnknown指针时,我会在其上调用IUnknown::AddRef以确保在我跟踪它时它不会被破坏。这不是我真正想要的;对象的生命周期应该与没有我的跟踪代码一样长(或短),所以我宁愿在对象上保持weak reference。每当删除对某个跟踪的COM对象的最后一个引用(因此对象被销毁)时,我希望得到通知,以便我可以更新我的簿记(例如,通过将我的列表中的指针设置为NULL) *

最好的方法是什么?现在,我正在修补所有已创建对象的(第一个)VTable,以便通过第一个vtable通知IUnknown::Release。但是,这不适用于从多个接口继承的COM接口(因此有多个vtable),但我不确定这是否真的是一个问题:给定Rules for Implementing QueryInterface,应该只有一个IUnknown::QueryInterface返回IUnknown,对吧?所以我可以做到这一点然后修补那个vtable。

此外,这种方法也有点毛茸茸,因为它涉及创建产生一些代码的thunk。到目前为止我只为32位实现了这个。这不是一个大问题,但仍然存在。

我真的想知道是否有一种更优雅的方式来对COM对象进行弱引用。有人知道吗?

*:我要解决的下一件事就是在我有活动迭代器(我使用自定义迭代器对象)遍历COM对象列表的情况下,使其正常工作。我可能需要跟踪活动迭代器,一旦完成最后一个迭代器,从列表中删除所有空指针。或类似的东西。

3 个答案:

答案 0 :(得分:4)

这不是一个问题的答案,为什么这是一个非常棘手的问题 - 我把它作为一个答案,因为这里的信息太多而不适合评论:)

我的理解是,COM中不存在弱引用的概念。你通过IUnknown得到了引用计数,这就是COM处理对象生命周期管理的总和。严格来说,除此之外的任何事情都不是COM。

(。Net确实支持这个概念,但它有一个实际的基于GC的内存管理器来提供适当的支持,并且可以不同于内存中的常规引用来处理WeakRef对象。但对于非常简单的COM世界来说情况并非如此假设,这是一个普通记忆和指针的世界,而且还有更多。)

COM指定引用计数是每个接口;为方便起见,任何COM对象都可以自由地对每个对象进行ref计数,但结果是如果你要包装一个对象,你必须承担最严格的情况。所以你不能假设任何给定的IUnknown将用于该对象上的所有addrefs / release:你真的需要单独跟踪每个接口。

规范的IUnknown - 你通过QI'ing为IUnknown得到的那个 - 可以是任何接口 - 甚至是专门用于作为身份的IUnknown! - 只要每次返回相同的二进制指针值。所有其他接口都可以以任何方式实现;通常每次返回相同的值,但每次有人为Qoo的QI时,COM对象可以合法地返回一个新的IFoo。或者甚至保留IFoos的缓存并随机返回。

...然后你有聚合来处理 - 基本上,COM根本没有强大的对象概念,它都是关于接口的。 COM中的对象只是碰巧共享相同规范IUnknown的接口集合:它们可能在幕后实现为单个C / C ++对象,或者作为一系列相关的C / C ++对象实现'单个COM对象'。


说完所有这些,鉴于:

为了调试,我正在跟踪该软件的各种组件(包括所有COM对象)的状态

这是一种替代方法,可能会产生一些有用的数据来调试。

这里的想法是COM对象的许多实现将返回引用计数作为Release()的返回值 - 所以如果它们返回0,那么这就是接口可能已被释放的线索。

但无法保证:正如MSDN所述:

该方法返回新的引用计数。 此值仅用于测试目的。

(重点补充。)

但这显然是你在这里所做的。

因此,假设您拥有调用代码,您可以做的一件事是使用Release()替换调用带有内联调用MyRelease()或类似调用release的调用,如果它注意到返回值为0,然后注意到接口指针现在可能被释放 - 将其从表中删除,将其记录到文件中等。

一个重要的警告:请记住,即使你试图将某些东西放在一起,COM也没有弱参照的概念。就COM而言,使用一个不是AddRef()的COM接口指针是非法的;因此,如果您将接口指针值保存在任何类型的列表中,那么您应该将它们视为不可见的数字用于调试目的(例如,将它们记录到文件中,以便您可以将创建与销毁相关联,或者跟踪你有多少未完成的,但不要试图将它们用作实际的接口指针。

同样,请记住,没有任何东西需要COM对象遵循返回引用计数的约定;所以请注意,你可以看到一些看起来像bug的东西但实际上只是Release的实现只是总是返回0(或rand(),如果你特别不走运!)

答案 1 :(得分:1)

首先,你是对的,IUnknown的QueryInterface应该总是返回相同的指针; IUnknown被视为对象的身份IIRC,因此需要保持稳定。

至于弱点,在我的头顶,也许你可以给CoMarshalInterThreadInterfaceInStream一个旋转?它允许您将对COM对象的引用序列化为流,然后使用该流在其他某个线程上创建对该对象的新引用。但是,如果序列化为流并将流保留为一种弱指针,然后解组以恢复指针,则可以检查解组是否失败;如果是这样,对象就消失了。

答案 2 :(得分:1)

使用WinRT添加IWeakReference以启用对COM对象的弱引用。使用WRL< RuntimeClass创建的对象默认支持IWeakReference(可以使用选项禁用)。

你可以在你的设计中使用IWeakReference,但这意味着你需要至少使用一些WinRT概念,基于IInspectable的界面。