如果我有一个IUnknown *ptr
,我是否需要在我通过ptr->QueryInterface()
获得的每个接口上调用Release()
, 除了当我完成ptr
时调用ptr->Release()
?
我曾经认为答案是"是",但是MSDN的这句话让我困惑:
偶尔你可能需要获得一个对象的弱引用(也就是说,你可能希望获得一个指向它的一个接口的指针而不增加引用计数),但是通过调用
QueryInterface
和Release
来实现这一点是不可接受的。
我不明白为什么这是有问题的——如果我调用ptr->QueryInterface()
,然后在结果指针上调用Release
,对象上的引用计数不应该仍然是正的吗?这是如何导致无效的指针?
文档正确。您还需要遵循引用计数规则——除了在创建对象之后,还包括在从QueryInterface
获得的接口上调用Release
。
为了澄清为什么你不能用Release
做弱指针——在调用QueryInterface
和Release
之后存在一个竞争条件。
- Thread1创建对象-引用计数1
- Thread2调用
QueryInterface
进行弱引用-引用计数2 - Thread1释放对象-引用计数1
- Thread2调用
Release
进行弱引用-引用计数为0。对象被销毁。 - Thread2试图使用object - error.
这个警告是为了防止上面的问题——可能一些程序员认为他们可以"调用ptr->QueryInterface()
,然后在结果指针上调用Release
",然后使用对象…
IUnknown:: QueryInterface方法
获取指向对象上支持的接口的指针。
该方法在返回的指针上调用IUnknown::AddRef。
直接来自IUnknown::QueryInterface reference athttp://msdn.microsoft.com/en-us/library/ms682521%28v=vs.85%29.aspx
标题不是唯一的场景;我想说的是,线程实际上根本不是主要的场景:这些COM规则可以追溯到Win16,在抢占式多线程被添加到Windows之前。
关键问题是,就COM而言,引用计数是每个接口,而不是每个对象。COM实现可以通过每个对象来实现引用计数——这可能是c++中最简单的实现方式,尤其是当COM对象映射到单个c++对象时——但这只不过是一个实现细节,COM客户端代码不能依赖于这种情况。有许多COM对象可以根据需要动态地生成接口,然后在不再需要它们时销毁它们。在这些情况下,如果您调用QI来获取这些接口之一,一旦您调用Release,该接口的内存就会被重新分配,因此使用它可能会导致故障/崩溃等。
一般来说,你必须把任何对->Release()的调用看作是潜在地释放指针后面的内存。
避免弱引用的建议并不能解决争用问题。
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方法来发布契约,并在内部管理争用。当然,也有服务器处理这种"扇形输出"的例子。看看Microsoft的持久存储接口。在那里,接口不会"扇出"…每个接口应该由单个用户(线程/进程/其他)拥有,"扇出"由服务器在内部管理,并向COM客户端提供方法来控制争用问题的某些方面。因此,微软的COM服务器必须将竞态条件作为它们与客户签订的合同的一部分。