【问题标题】:Is using implicit conversion for an upcast instead of QueryInterface() legal with multiple inheritance?在多重继承中使用隐式转换而不是 QueryInterface() 合法吗?
【发布时间】:2011-03-12 04:09:21
【问题描述】:

假设我有一个实现两个或多个 COM 接口的类(完全如 here):

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

现在对象中有两个IUnknowns - 一个是IInterface1 的基,另一个是IInterface2 的基。还有they are in different subobjects

假设我为IInterface2 调用QueryInterface() - 返回的指针与我为IUnknown 调用QueryInterface() 时返回的指针不同。到目前为止,一切都很好。然后我可以将检索到的IInterface2* 传递给任何接受IUnknown* 的函数,并且由于C++ 隐式转换,该指针将被接受,但它与QueryInterface() for IUnknown* 检索的指针不同。事实上,如果该函数在被调用后立即为IUnknown 调用QueryInterface(),它将检索一个不同的指针。

这在 COM 方面是否合法?当我有一个指向多重继承对象的指针并且我允许隐式向上转换时,我该如何处理?

【问题讨论】:

  • 实际上,我从未见过任何代码使用 COM 身份规则。也就是说,另一个 IUnknown* 是不合法的 - 您必须选择一个从 QueryInterface 返回。就您自己的、内部的、C++ 对 COM 对象实现对象的使用而言 - 如果您正在铸造,那么您无论如何都不会做 COM,所以您所做的任何事情都是合法的 C++,而不是合法的 COM。
  • @Chris Becke:我猜想实现一些类似缓存的功能需要身份。

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


【解决方案1】:

COM 没有关于接口标识的规则,只有对象标识。 QI 的第一条规则说,如果两个接口指针上的 IID_Unknown 上的 QI 由相同的对象实现,则它们必须返回相同的指针。您的 QI 实现正确地做到了这一点。

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

【讨论】:

    【解决方案2】:

    正如Hans 指出的那样,您对QueryInterface 的实现是正确的。

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

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

    它是否会导致应用程序失败取决于应用程序是否关心比较 COM 对象的身份。

    MSDN: The Rules of the Component Object Model

    对象标识。 要求 任何对 QueryInterface 的调用 给定对象实例的接口 对于特定接口 IUnknown 必须始终返回相同的物理 指针值。这可以调用 QueryInterface(IID_IUnknown, ...) on 任意两个接口并比较 结果来确定他们是否 指向同一个实例 对象(相同的 COM 对象标识)。

    正如Oleg 所指出的,对象标识的失败将产生相当有限的影响,因为COM 接口成员的调用基本上不受影响——如果函数签名匹配,每个虚拟表条目将指向相同的函数地址。

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

    当类没有任何菱形继承时,失败不会出现的一种特殊情况。然而,隐式转换在 COM 中始终是非法的,无论它是否使应用程序崩溃。

    【讨论】:

    • 我已经阅读了规则,但是......他们说QueryInterface(),而不是其他任何东西,必须遵循身份要求。这真的意味着隐式向上转型是非法的吗? QI()QI(),C++ 转换就是 C++ 转换。
    • 我不同意。引文谈到对象身份,与接口身份不同。
    • @sharptooth:在 COM 中思考时,尽量远离 IInterface1 继承自 IUnknown 的想法。更准确的说法是:IInterface1 包含成员 AddRef、Release 和 QueryInterface 以及它自己的函数。
    • @Hans:我承认可能存在混淆。这个问题的标题是“对向上转换而不是 QueryInterface 的隐式转换”。在@sharptooth 的问题中询问允许这种情况发生是否违法。 (我将编辑我的答案以澄清)
    • 不管怎样,规则说对同一个对象的同一个接口的两个 QI 调用必须返回同一个指针。 upcast 不会违反此要求 - QI 仍然可以正常工作。
    【解决方案3】:

    似乎有一个小误会。接口IInterface1IInterface2 是纯粹抽象的。 IInterface1IInterface2 没有单独的 QueryInterface()。只有一个声明,该类CMyClass 将实现来自IInterface1IInterface2 的所有方法(CMyClass 的方法集是IInterface1IInterface1 的方法集)。

    所以你在 CMyClass 类中实现 one QueryInterface()one AddRef()one Release() 方法。在QueryInterface() 中,您将指向CMyClass 类实例的指针转换为static_cast&lt;IUnknown*&gt;

    已更新:嗨!写完答案后,我不得不立即开车离开。直到现在我才能阅读所有其他答案,并可以在我的答案中添加一些内容。

    好的。您说如果您将IInterface1 转换为IUnknown,并且如果您将IInterface2 转换为IUnknown,您会收到两个不同的指针。你说的对!但是,这并不重要。如果你比较两个指针的包含(在这两种情况下,地址有QueryInterface()AddRef()Release())你会发现两个指针有相同的包含。所以我也是对的!

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

    还有一点小话。在某些 cmets 中,您写到有许多方法实现 QueryInterface()AddRef()Release() 的可能性。我不同意这里。原因是接口是纯抽象类,如果你定义一个实现了一些接口的类,你就没有典型的类继承。您只有一个类,所有接口的所有功能的一个实现。如果您在 C++ 中执行此操作,那么编译器会创建静态 vtable 并使用指向函数 QueryInterface()AddRef()Release() 等的唯一实现的相应指针填充静态 vtable,但所有 vtable 都指向相同的函数。

    为了减少 Microsoft 引入的 vtable 数量 __declspec(novtable)ATL_NO_VTABLE,但这不是您问题的一部分。

    【讨论】:

    • 是的,我知道。但是有一个正式的问题。当我从IInterface2 隐式向上转换到IUnknown 时,我得到的指针与我从QI() 得到的指针不同。整个问题是关于一件事:就 COM 而言,IUnknown“合法”吗?
    • 这些方法有很多实现,即使只有一个将调用转发到实现它的单个版本。
    【解决方案4】:

    如果您有interface IInterface1 : IDispatchinterface IInterface2 : IDispatch,那么QI 用于IUnknown 上的IInterface1IInterface2 必须根据对象身份规则返回相同的指针

    但是……

    IDispatch 上的IInterface1 上的QIQI 上的IDispatch 上的IInterface2 相比,可以返回不同的实现。

    所以答案是(再次)视情况而定。向上转换为 IUnknown 为负值,向上转换为其他任何值可能为正值。

    【讨论】:

      猜你喜欢
      • 2011-02-09
      • 1970-01-01
      • 2015-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多