8

如果我有一个,除了在完成后调用之外,IUnknown *ptr我还需要调用Release()我通过 获得的每个接口吗?ptr->QueryInterface()ptr->Release()ptr

我曾经认为答案是“是”,但MSDN 的这句话让我很困惑:

有时您可能需要获取对对象的弱引用(也就是说,您可能希望在不增加引用计数的情况下获取指向其接口之一的指针),但通过调用QueryInterface后跟Release.

我不明白为什么会有问题——如果我调用ptr->QueryInterface()然后调用Release结果指针,对象上的引用计数不应该仍然是正数吗?这如何导致无效指针?

4

4 回答 4

8

文档是正确的。并且您需要遵循引用计数规则 - 包括在创建对象之后调用Release从获取的接口。QueryInterface

为了弄清楚为什么你不能做弱指针- 在调用和之后立即Release存在竞争条件。QueryInterfaceRelease

  • Thread1 创建对象 - 引用计数 1
  • Thread2 调用QueryInterface弱引用 - 引用计数 2
  • Thread1 释放对象 - 引用计数 1
  • Thread2 调用Release弱引用 - 引用计数 0。对象被破坏。
  • Thread2 尝试使用对象 - 错误。

警告是为了防止上述情况发生 - 大概一些程序员认为他们可以“调用ptr->QueryInterface()然后调用Release结果指针”然后使用对象......

于 2011-09-11T04:28:27.970 回答
4

IUnknown::QueryInterface 方法

检索指向对象上支持的接口的指针。

此方法在它返回的指针上调用 IUnknown::AddRef。

直接来自http://msdn.microsoft.com/en-us/library/ms682521%28v=vs.85%29.aspx的 IUnknown::QueryInterface 参考

于 2011-09-11T04:27:53.327 回答
2

Theading 不是唯一的场景。我什至可以说线程实际上根本不是主要场景:这些 COM 规则可以追溯到 Win16,然后先将抢占式多线程添加到 Windows。

关键问题是,就 COM 而言,引用计数是每个接口的,而不是每个对象的。COM 实现可以自由地通过按对象实现引用计数来实际实现引用计数——这可能是在 C++ 中实现它的最简单方法,尤其是当 COM 对象映射到单个 C++ 对象时——但这只不过是一个实现细节,并且 COM 客户端代码不能依赖它。

有许多 COM 对象可以根据需要动态生成接口,然后在不再需要时立即销毁它们。在这些情况下,如果您调用 QI 来获取这些接口之一,一旦您调用 Release,该接口的内存就会被释放,因此使用它可能会导致故障/崩溃/等。

一般来说,您必须将任何对 ->Release() 的调用视为潜在地释放指针后面的内存。

(另外,请注意 COM 并没有真正开始弱引用的概念:有计数(强)引用,仅此而已。)

于 2011-09-11T09:19:36.217 回答
0

避免弱引用的建议并不能解决竞争问题。

T1 operator new, create object, references: 1
T1     passes interface object reference to T2, thinking it can "share" ownership
T1     suspends
T2     resumes
T2 QueryInterface
T2     suspends before InterlockedIncrement, references: 1
T1     resumes
T1 Calls Release
T1     suspends between InterlockedDecrement and operator delete, references: 0
T2     resumes, InterlockedIncrement occurs, references 1
T2     suspends
T1     resumes, operator delete executes, references 1 !!!
T1     suspends
T2     resumes
T2 Any reference to the interface is now invalid since it has been deleted with reference count 1.

这可以在 COM 服务器中解决。然而,COM 客户端不应依赖于阻止这种竞争条件的服务器。因此,COM 客户端不能在线程之间共享接口对象。唯一应该被允许访问接口对象的线程是当前“拥有”接口对象的一个​​线程。

T1 不应该调用 Release。是的,它可以在将接口对象传递给 T2 之前调用 AddRef。但这可能无法解决比赛,只能将其移至其他地方。最佳实践是始终保持一个接口对象、一个所有者的概念。

如果 COM 服务器希望支持两个(或更多)接口可以引用某些共享的服务器内部状态的概念,则 COM 服务器应通过提供 CreateCopyOfInstance 方法来通告合同并在内部管理争用。当然,也有处理这种“扇出”的服务器示例。看看微软的持久存储接口。在那里,接口不会“扇出”..每个接口都应该由单个用户(线程/进程/任何东西)拥有,并且“扇出”在内部由服务器管理,并提供给COM 客户端控制争用问题的某些方面。因此,Microsoft 的 COM 服务器必须将竞争条件作为其与客户的合同的一部分。

于 2013-03-14T05:33:43.850 回答