2010-07-06 49 views
5

假设我有一个类实现两个或多个COM接口(完全一样here):使用隐式转换为上传而不是QueryInterface()合法与多继承?

class CMyClass : public IInterface1, public IInterface2 { 
}; 

QueryInterface()必须返回相同的指针相同的接口的每个请求(它需要一个显式上溯造型适当指针调整) :

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; 
} 

现在的目标是2 IUnknown秒 - 一个是IInterface1的基础,另一个是IInterface2基础。和。

让我们假设我打电话QueryInterface()IInterface2 - 当我打电话QueryInterface()IUnknown返回将从指针不同的指针返回。到现在为止还挺好。然后,我可以将检索到的IInterface2*转换为接受IUnknown*的任何函数,并且感谢C++隐式转换,指针将被接受,但将检索QueryInterface()不同于指针。实际上,如果该函数在调用后立即调用QueryInterface(),IUnknown它将检索不同的指针。

这是合法的COM?当我有一个指向多重继承对象的指针并且允许隐式预测时,我该如何处理情况?

+0

实际上,我从来没有见过任何代码使用COM标识规则。也就是说,另一个IUnknown *不合法 - 您必须选择一个从QueryInterface返回。 就你自己的,内部的C++使用COM对象实现对象而言 - 如果你投你不做COM,那么你所做的任何事情都是合法的C++,而不是Legitimage COM。 – 2010-07-06 11:26:53

+0

@Chris Becke:我猜身份是实现一些类似缓存的功能所必需的。 – sharptooth 2010-07-07 07:27:38

回答

3

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

如果没有接口标识的保证,COM方法不能认为它获得了相同的IUnknown指针传递,它会在该指针上调用QI时检索它。所以如果需要证明对象身份,那么需要单独的QI。

2

看来还有一个小小的误区。接口IInterface1IInterface2是抽象的。 IInterface1IInterface2没有单独的QueryInterface()。只有一个声明,类CMyClass将实施IInterface1IInterface2CMyClass的方法集合方法IInterface1IInterface1在一起)的所有方法。

所以,你在班里CMyClass一个QueryInterface()实施,一个AddRef()一个Release()方法。在QueryInterface()中,您将指向CMyClass的实例的指针投射到static_cast<IUnknown*>

已更新:嗨!写完我的回答后,我不得不马上开车。只有现在我可以阅读所有其他答案,并可以添加一些东西给我的答案。

好的。你说如果你投了IInterface1IUnknown,如果你投IInterface2IUnknown你会收到两个不同的指针。你是对的!但不过没关系。如果您比较包含指针(在两种情况下地址都有QueryInterface(),AddRef()Release()),您将看到两个指针都具有相同的包含。所以我也是对的!

有很多很好的例子,说明如何在纯C中实现COM。在这种情况下,您需要定义一个静态结构,指向虚拟函数的指针,如QueryInterface(),AddRef()Release(),并将结构的指针返回为QueryInterface()的结果。它再次表明,只有你给出的包含对于COM而言是重要的,而不是一个指针(它是不重要的)。

还有一点小话。在一些评论中,你写了关于方法QueryInterface(),AddRef()Release()的许多实现的可能性。我在这里不同意。原因是接口是纯抽象类,如果您定义了实现某些接口的类,则不会有典型的类继承。您只有一个类,从所有接口执行所有功能。如果你在C++中这样做,那么编译器会创建并填充静态vtables,其中包含相应的指针,指向函数QueryInterface(),AddRef(), Release()等的唯一实现,但是所有vtables都指向相同的函数。

为了减少微软推出的vtables的数量__declspec(novtable)ATL_NO_VTABLE,但这不是你问题的一部分。

+0

是的,我知道。但是有一个正式的问题。当我隐式从'IInterface2'上传到'IUnknown'时,我得到的指针与我从'QI()'得到的指针不同。整个问题是关于一件事情:就COM而言,“IUnknown”是否“合法”? – sharptooth 2010-07-06 07:20:41

+0

这些方法有许多实现,即使除一个方法外,其他方法都会将调用转发给实现它的单个版本。 – 2010-07-06 07:38:35

2

由于Hans指出,你的执行QueryInterface是正确的。

COM对象的用户的责任是始终使用QueryInterface。原因正是为了防止您指出的情况:将IInterface1 *或IInterface2 *指针投射到IUnknown *指针将产生不同的物理值。

在C++中,实现者不可能阻止用户做错。

是否会导致应用程序失败取决于应用是否在乎有关身份比较COM对象。

MSDN: The Rules of the Component Object Model

对象的身份。这是要求 到任何调用QueryInterface任何 接口上给定对象实例 特定接口的IUnknown 必须始终返回相同的物理 指针值。这使得调用 的QueryInterface(IID_IUnknown,...)上 任何两个接口,比较 结果以确定它们是否 点到 对象(同COM对象的身份)的同一个实例。

由于Oleg指出,对象身份的失败将有一个相当有限的影响,因为COM接口部件的调用是基本上不受影响 - 如果函数签名相匹配的每个虚拟表项将指向相同的函数地址。

所有COM智能指针实现使用QueryInterface时转换到不同的接口,或当当前接口是可疑的。他们的比较运算符在每个输入指针上自动使用QueryInterface(IID_IUnknown, ...),以获得可以直接比较的物理IUnknown *指针。如果您选择在应用程序中使用原始指针,对象身份的失败将开始影响您的应用程序。

失败不会显示的一个特例是类没有任何钻石继承。但是,无论是否使应用程序崩溃,隐式转换在COM中都是非法的。

+0

我读过规则,但是......他们说'QueryInterface()',而不是别的,必须遵循标识要求。这真的意味着一个隐式的预报是非法的吗? 'QI()'是'QI()',C++转换是C++转换。 – sharptooth 2010-07-06 07:31:00

+0

我不同意。引用关于*对象标识*,与接口标识不同。 – 2010-07-06 07:39:18

+1

@sharptooth:在COM中思考时,尽量远离IInterface1从IUnknown继承的思想。更准确的说法是:IInterface1包含成员AddRef,Release和QueryInterface以及它自己的函数。 – rwong 2010-07-06 07:39:36

0

如果你有interface IInterface1 : IDispatchinterface IInterface2 : IDispatch然后QIIUnknownIInterface1IInterface2必须返回每个对象的身份规则相同的指针

但是......

QIIDispatchIInterface1可以返回比较不同的实现QI为。

所以答案是(再次)它取决于。对于上传到IUnknown的负面影响,可能会对上传到其他任何内容产生积极影响。