假设我有一个实现两个或更多COM接口的类(与here完全相同):
class CMyClass : public IInterface1, public IInterface2 {
};
QueryInterface()
必须为同一个接口的每个请求返回相同的指针(它需要显式的upcast才能正确调整指针):
if( iid == __uuidof( IUnknown ) ) {
*ppv = static_cast<IInterface1*>( this );
//call Addref(), return S_OK
} else if( iid == __uuidof( IInterface1 ) ) {
*ppv = static_cast<IInterface1*>( this );
//call Addref(), return S_OK
} else if( iid == __uuidof( IInterface2 ) ) {
*ppv = static_cast<IInterface2*>( this );
//call Addref(), return S_OK
} else {
*ppv = 0;
return E_NOINTERFACE;
}
现在对象中有两个IUnknown
- 一个是IInterface1
的基础,另一个是IInterface2
的基础。并they are in different subobjects。
让我假装我为QueryInterface()
调用IInterface2
- 返回的指针与我为QueryInterface()
调用IUnknown
时返回的指针不同。到现在为止还挺好。然后我可以将检索到的IInterface2*
传递给任何接受IUnknown*
的函数,并且由于C ++隐式转换,指针将被接受,但它与QueryInterface()
IUnknown*
的指针不同会检索。实际上,如果该函数在被调用时立即调用QueryInterface()
IUnknown
,它将检索不同的指针。
这在COM方面是否合法?当我有一个指向多重继承对象的指针并允许隐式向上转换时,如何处理这种情况?
答案 0 :(得分:3)
COM没有关于接口标识的规则,只有对象标识的规则。 QI的第一条规则是,两个接口指针上的IID_Unknown上的QI必须返回相同的指针,如果它们由相同的对象实现的话。您的QI实现正确执行此操作。
如果没有接口标识的保证,COM方法不能假设它获得了相同的IUnknown指针,当它在该指针上调用QI时它将检索它。因此,如果需要证明对象身份,则需要单独的QI。
答案 1 :(得分:2)
似乎存在一个小小的误解。接口IInterface1
和IInterface2
是抽象的。 QueryInterface()
和IInterface1
没有单独的IInterface2
。只有声明,类CMyClass
将实现IInterface1
和IInterface2
的所有方法(CMyClass
的方法集是方法集IInterface1
和IInterface1
在一起)。
因此,您要在课程CMyClass
一个 QueryInterface()
,一个 AddRef()
和一个中实施Release()
方法。在QueryInterface()
中,您将指向类CMyClass
的实例的指针投射到static_cast<IUnknown*>
。
更新:嗨!在写完答案之后我不得不马上离开。只有现在我才能阅读所有其他答案,并可以在我的答案中添加一些内容。
行。你说如果你将IInterface1
转换为IUnknown
,并且如果你将IInterface2
转换为IUnknown
,则会收到两个不同的指针。你是对的!但无论如何都没关系。如果您比较两个指针的包含(在两种情况下哪些地址都有QueryInterface()
,AddRef()
和Release()
),您会看到两个指针都包含相同的内容。所以我也是对的!
如何在纯C中实现COM有很多很好的示例。在这种情况下,您需要定义一个静态结构,其中包含指向虚拟函数的指针,如QueryInterface()
,AddRef()
和{{1}然后将Release()
的结果返回给这样的结构的指针。它再次显示,只有你给出的包含对COM很重要而不是一个指针(你回放哪个vtable并不重要)。
还有一个小小的评论。在一些评论中,您写了关于方法QueryInterface()
,QueryInterface()
和AddRef()
的许多实现的可能性。我在这里不同意。原因是接口是纯抽象类,如果定义实现某些接口的类,则没有典型的类继承。您只有一个类,其中一个实现了所有接口的所有功能。如果你在C ++中这样做,那么编译器创建并填充静态vtable,其中包含指向函数Release()
,QueryInterface()
,AddRef()
等唯一实现的相应指针,但所有vtable点相同的功能。
减少微软推出的Release()
或__declspec(novtable)
的vtables数量,但这不是您问题的一部分。
答案 2 :(得分:2)
正如Hans所指出的那样,QueryInterface
的实施是正确的。
COM对象的用户有责任始终使用QueryInterface
。原因正是为了防止你指出的场景:向IUnknown *指针投射IInterface1 *或IInterface2 *指针会产生不同的物理值。
在C ++中,实现者不可能阻止用户做错。
是否会导致应用程序失败取决于应用程序 关心 关于比较COM对象的身份。
MSDN: The Rules of the Component Object Model
对象标识。 这是必需的 任何对 QueryInterface 的任何调用 给定对象实例的接口 对于特定接口 IUnknown 必须始终返回相同的物理 指针值。这使得呼叫 QueryInterface(IID_IUnknown,...) on 任何两个接口并比较 结果来判断他们是否 指向一个相同的实例 object(相同的COM对象标识)。
正如Oleg所指出的,对象标识的失败将产生相当有限的影响,因为COM接口成员的调用基本上不受影响 - 如果函数签名,每个虚拟表条目将指向相同的函数地址匹配。
当转换到不同的接口时,或者当前接口可疑时,所有COM智能指针实现都使用QueryInterface
。它们的比较运算符会在每个输入指针上自动使用QueryInterface(IID_IUnknown, ...)
,以便获得可以直接比较的物理IUnknown *指针。如果您选择在整个应用程序中使用原始指针,则对象标识的失败将开始影响您的应用程序。
失败不会表现的一个特例是当班级没有任何钻石继承时。但是,隐式转换在COM中始终是非法的,无论它是否会使应用程序崩溃。
答案 3 :(得分:0)
如果您有interface IInterface1 : IDispatch
和interface IInterface2 : IDispatch
,那么QI
IUnknown
IInterface1
和IInterface2
必须为每个对象标识规则返回相同的指针
但是...
QI
上IDispatch
的{{1}} <{1}}可以返回与IInterface1
上QI
的{{1}}不同的实现。
所以答案是(再次)取决于。向上升到IDispatch
的负面信号可能对向其他任何事物的向上倾斜都是正面的。