使用隐式转换进行upcast而不是QueryInterface()合法使用多重继承?

时间:2010-07-06 06:38:00

标签: c++ windows visual-c++ com multiple-inheritance

假设我有一个实现两个或更多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方面是否合法?当我有一个指向多重继承对象的指针并允许隐式向上转换时,如何处理这种情况?

4 个答案:

答案 0 :(得分:3)

COM没有关于接口标识的规则,只有对象标识的规则。 QI的第一条规则是,两个接口指针上的IID_Unknown上的QI必须返回相同的指针,如果它们由相同的对象实现的话。您的QI实现正确执行此操作。

如果没有接口标识的保证,COM方法不能假设它获得了相同的IUnknown指针,当它在该指针上调用QI时它将检索它。因此,如果需要证明对象身份,则需要单独的QI。

答案 1 :(得分:2)

似乎存在一个小小的误解。接口IInterface1IInterface2是抽象的。 QueryInterface()IInterface1没有单独的IInterface2。只有声明,类CMyClass将实现IInterface1IInterface2的所有方法(CMyClass的方法集是方法集IInterface1IInterface1在一起)。

因此,您要在课程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 : IDispatchinterface IInterface2 : IDispatch,那么QI IUnknown IInterface1IInterface2必须为每个对象标识规则返回相同的指针

但是...

QIIDispatch的{​​{1}} <{1}}可以返回与IInterface1QI的{​​{1}}不同的实现。

所以答案是(再次)取决于。向上升到IDispatch的负面信号可能对向其他任何事物的向上倾斜都是正面的。