在我的应用程序中,我挂钩了用于创建 COM 对象的各种函数(例如 CoCreateInstanceEx
) 以便在创建某个对象时收到通知。我正在跟踪 std::list
中创建的所有对象 我正在迭代该列表以执行各种操作(例如检查哪些 OLE 对象已被激活)。
现在的问题是,每当添加 IUnknown
指向我的列表,我调用 IUnknown::AddRef
来确保它在我跟踪时不会被破坏。但这并不是我真正想要的;对象的生命周期应该与没有跟踪代码时一样长(或短),所以我宁愿维护一个 对象的弱引用。每当对某个跟踪的 COM 对象的最后一个引用被删除(因此该对象被销毁)时,我希望收到通知,以便我可以更新我的簿记(例如,通过将列表中的指针设置为 NULL代码>).*
最好的方法是什么?现在,我正在修补所有创建的对象的(第一个)VTable,以便调用 IUnknown::Release
通过第一个 vtable 获得通知。但是,这不适用于从多个接口继承的 COM 接口(因此具有多个 vtable),但我不确定这是否真的是一个问题:鉴于 实现 QueryInterface 的规则,应该始终只有一个 IUnknown
由 IUnknown::QueryInterface
,对吗?所以我可以这样做,然后修补该 vtable。
此外,这种方法也有点麻烦,因为它涉及创建生成一些代码的 thunk。到目前为止我只针对 32 位实现了这个。虽然不是大问题,但仍然如此。
我真的想知道是否没有更优雅的方法来弱引用 COM 对象。有人知道吗?
*:接下来我要解决的问题是,如果我有活动迭代器(我正在使用自定义迭代器对象)遍历 COM 对象列表,则使其正常工作。我可能需要跟踪活动迭代器,当最后一个迭代器完成后,从列表中删除所有空指针。或者类似的东西。
In my application, I'm hooking various functions for creating COM objects (such as CoCreateInstanceEx
) to get notified whenever some object is created. I'm keeping track of all created objects in a std::list
and I'm iterating over that list to do various things (like checking which OLE objects have been activated).
The issue with this is that right now, whenever adding an IUnknown
pointer to my list, I call IUnknown::AddRef
on it to make sure that it doesn't get destroyed while I'm tracking it. That's not what I really want though; the lifetime of the object should be as long (or short) as it is without my tracing code, so I'd rather like to maintain a weak reference on the objects. Whenever the last reference to some tracked COM object is removed (and thus the object gets destroyed), I'd like to get notified so that I can update my bookkeeping (e.g. by setting the pointer in my list to NULL
).*
What's the best way to do this? Right now, I'm patching the (first) VTable of all created objects so that the calls to IUnknown::Release
via the first vtable get notified. However, this won't work for COM interfaces which inherit from multiple interfaces (and thus have multiple vtables), but I'm not sure whether this is really a problem: given the Rules for Implementing QueryInterface, there should always be just one IUnknown
returned by IUnknown::QueryInterface
, right? So I could do that and then patch that vtable.
Furthermore, this approach is also a bit hairy since it involves creating thunks which generate some code. I only implemented this for 32bit so far. Not a big issue, but still.
I'm really wondering whether there isn't a more elegant way to have a weak reference to a COM object. Does anybody know?
*: The next thing I'll have to solve is making this work correctly in case I have active iterators (I'm using custom iterator objects) traversing the list of COM objects. I may need to keep track of the active iterators and once the last one finished, remove all null pointers from the list. Or something like that.
发布评论
评论(4)
这不是一个答案,而是一系列问题,为什么这是一件非常棘手的事情 - 我将其作为答案,因为这里的信息太多,无法在评论中容纳:)
我的理解是, COM 中不存在弱引用的概念。您已经通过 IUnknown 获得了引用计数,这就是 COM 处理对象生命周期管理的方式的总和。严格来说,超出此范围的任何内容都不是 COM。
(.Net 确实支持这个概念,但它有一个实际的基于 GC 的内存管理器来提供适当的支持,并且可以以不同于内存中常规引用的方式处理 WeakRef 对象。但 COM 假设的非常简单的世界并非如此,它 的
COM 指定引用计数是针对每个接口 ;为了方便起见,任何 COM 对象都可以自由地对每个对象进行引用计数,但结果是,如果您要包装对象,则必须假设最严格的情况。因此,您不能假设任何给定的 IUnknown 将用于该对象上的所有 addrefs/releases:您确实需要单独跟踪每个接口。
规范的 IUnknown - 通过 QI'ing 获取 IUnknown 得到的接口 - 可以是任何接口 - 甚至是仅用于充当身份的专用 IUnknown! - 只要每次返回相同的二进制指针值。所有其他接口都可以以任何方式实现;通常每次都会返回相同的值,但每次有人 QI 获取 IFoo 时,COM 对象都可以合法地返回一个新的 IFoo。或者甚至保留 IFoos 缓存并随机返回一个。
...然后你需要处理聚合 - 基本上,COM 根本没有强大的对象概念,它都是关于接口的。 COM 中的对象只是碰巧共享相同规范 IUnknown 的接口的集合:它们可能在幕后实现为单个 C/C++ 对象,或者作为呈现外观的一系列相关 C/C++ 对象。 “单个 COM 对象”。
说了这么多,考虑到:
这是一种替代方法,可能会产生一些有用的数据进行调试。
这里的想法是,COM 对象的许多实现将返回引用计数作为 Release() 的返回值 - 因此,如果它们返回 0,则表明该接口可能已被释放。
但是,这并不能保证:如 MSDN 指出:
(强调。)
但这显然就是你在这里所做的。
因此,假设您拥有调用代码,您可以做的一件事是将 Release() 的调用替换为名为 MyRelease() 的内联函数或类似的将调用release的函数,并且如果它注意到返回值为 0,则指出接口指针现在可能已被释放 - 将其从表中删除,将其记录到文件中,等等。
一个主要警告:请记住,COM 没有弱引用的概念,即使您尝试一起破解某些东西。对于 COM 而言,使用未经 AddRef() 处理的 COM 接口指针是非法的;因此,如果您将接口指针值保存在任何类型的列表中,您唯一应该做的就是将它们视为不透明的数字以进行调试(例如,将它们记录到文件中,以便您可以将创建与销毁相关联,或跟踪有多少个未完成的),但不要尝试将它们用作实际的接口指针。
再次请记住,没有什么要求 COM 对象遵循返回引用计数的约定;因此请注意,您可能会看到一些看起来像错误的东西,但实际上只是 Release 的实现,只是碰巧总是返回 0(或 rand(),如果您特别不幸的话!)
This isn't an answer as much as a set of issues why this is a really tricky thing to do - I'm putting it in as an answer since there's too much information here than fits in a comment :)
My understanding is that the concept of weak reference just doesn't exist in COM, period. You've got reference counting via IUnknown, and that's the sum total of how COM deals with object lifetime management. Anything beyond that is, strictly speaking, not COM.
(.Net does support the concept, but it's got an actual GC-based memory manager to provide appropriate support, and can treat WeakRef objects differently than regular references in memory. But that's not the case with the very simple world that COM assumes, which is a world of plain memory and pointers, and little more.)
COM specifies that reference counting is per-interface; any COM object is free to do ref counting per object as a convenience, but the upshot is that if you're wrapping an object, you have to assume the most restrictive case. So you cannot assume that any given IUnknown will be used for all addrefs/releases on that object: you'd really need to track each interface separately.
The canonical IUnknown - the one you get back by QI'ing for IUnknown - could be any interface at all - even a dedicated IUnknown that is used only for the purpose of acting as an identity! - so long as the same binary pointer value is returned each time. All other interfaces could be implemented any way; typically the same value is returned each time, but a COM object could legitimately return a new IFoo each time someone QI's for IFoo. Or even keep around a cache of IFoos and return one at random.
...and then you've got aggregation to deal with - basically, COM doesn't have a strong concept of object at all, it's all about interfaces. Objects, in COM, are just a collection of interfaces that happen to share the same canonical IUnknown: they might be implemented as a single C/C++ object behind the scenes, or as a family of related C/C++ objects presenting a facade of a 'single COM object'.
Having said all of that, given that:
Here's an alternate approach that might produce some useful data to debug with.
The idea here is that many implementations of COM objects will return the ref count as the return value to Release() - so if they return 0, then that's a clue that the interface may have been released.
This is not guaranteed, however: as MSDN states:
(emphasis added.)
But that's apparently what you're doing here.
So one thing you could do, assuming you own the calling code, is to replace calls with Release() with an inline called MyRelease() or similar that will call release, and if it notices that the return value is 0, then notes that the interface pointer is now possibly freed - removes it from a table, logs it to a file, etc.
One major caveat: keep in mind that COM does not have a concept of weak ref, even if you try to hack something together. Using a COM interface pointer that has not been AddRef()'d is illegal as far as COM is concerned; so if you save away interface pointer values in any sort of list, the only thing you should so with those is treat them as opaque numbers for debugging purposes (eg. log them to a file so you can correlate creates with destroys, or keep track of how many you have outstanding), but do not attempt to use them as actual interface pointers.
Again, keep in mind that nothing requires a COM object to follow the convention of returning the refcount; so be aware that you could see something that looks like a bug but is actually just an implementation of Release just happens to always returns 0 (or rand(), if you're especially unlucky!)
WinRT 中添加了 IWeakReference,以启用对 COM 对象的弱引用。使用 WRL 的 RuntimeClass 创建的对象默认支持 IWeakReference(可以通过选项禁用)。
您可以在设计中使用 IWeakReference,但这意味着您将需要至少使用一些 WinRT 概念、基于 IInspectable 的界面。
With WinRT IWeakReference was added to enable weak refs to COM objects. Objects created with WRL's RuntimeClass support IWeakReference by default (can be disabled with an option).
you can use IWeakReference in your designs but it means you will need to use at least some WinRT concepts, IInspectable based interface.
首先,您是对的,IUnknown 的 QueryInterface 应该始终返回相同的指针; IUnknown被视为对象的身份IIRC,因此需要稳定。
至于弱指针,我不知道,也许你可以尝试一下 CoMarshalInterThreadInterfaceInStream?它的目的是允许您将对 COM 对象的引用序列化到流中,然后使用该流在其他线程上创建对该对象的新引用。但是,如果您序列化到流中并将流保留为一种弱指针,然后稍后解组以恢复指针,则可以检查解组是否失败;如果是这样,该对象就消失了。
First, you're right that QueryInterface for IUnknown should always return the same pointer; IUnknown is treated as the object's identity IIRC, so needs to be stable.
As for weak pointers, off the top of my head, maybe you could give CoMarshalInterThreadInterfaceInStream a whirl? It is meant to allow you to serialize a reference to a COM object into a stream, then create a new reference to the object on some other thread using the stream. However, if you serialise into a stream and retain the stream as a sort of weak pointer, then unmarshal later on to recover the pointer, you could check whether unmarshalling fails; If so, the object is gone.
我建议您查看 https://github.com/forderud/MiniCOM 存储库它包含一个通过
IWeakRef
接口用于非拥有弱引用的SharedRef
包装类。这与IWeakReference
类似,但也与基于经典IUnknown
的 COM 兼容。I would recommend you to take a look at the https://github.com/forderud/MiniCOM repo that contains a
SharedRef
wrapper class for non-owning weak references through aIWeakRef
interface. This is similar toIWeakReference
, but is also compatible with classicalIUnknown
-based COM.