实现QueryInterface接口

本文档介绍正确地实现QueryInterface()的方式 

QueryInterface的参考实现

NS_IMETHODIMP
nsMyImplementation::QueryInterface( REFNSIID aIID, void** aInstancePtr )
  {
    NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
      // It's a logic error, not a runtime error, to call me without any place to put my answer!

      // ...but that won't matter when someone calls me wrongly in a non-debug build.
    if ( !aInstancePtr )
      return NS_ERROR_NULL_POINTER;

    nsISupports* foundInterface;

    if ( aIID.Equals(nsCOMTypeInfo<nsIX>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIX*, this);
    else if ( aIID.Equals(nsCOMTypeInfo<nsIY>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIY*, this);

    // ...as many cases as needed...

    else if ( aIID.Equals(nsCOMTypeInfo<nsISupports>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsISupports*, NS_STATIC_CAST(nsIX*, this));
        // I (may) have multiple |nsISupports| in me,
        //  so first I cast to a specific base to avoid ambiguity
    else
      foundInterface = 0;


    nsresult status;
    if ( !foundInterface )
      status = NS_NOINTERFACE;
    else
      {
        NS_ADDREF(foundInterface);
        status = NS_OK;
      }

    *aInstancePtr = foundInterface;
    return status;
  }

有什么好处呢?

代码优点:

  • 简单明了。
  • 好的。它有多个 return,但正如预期的那样,主要的 return 位于函数的末尾;并且附加的 return清晰而单独地位于函数的顶部。
  • 它只有一个AddRef
  • AddRef是生成的接口,不是this,因此遵循 COM 正确的方式(在聚合中尤其重要)
  • 它使用nsCOMTypeInfo<T>::GetIID()而不是kTIID节省全局声明和全局空间
  • 它使用C的NS_STATIC_CAST 定义static_cast当您无法真正获得所需的接口时,它将检测错误。
  • 它避免了反复使用和分配给*aInstancePtr,这些会使编译器在优化时遇到麻烦。
  • *aInstancePtr返回错误时,将清除结果
  • 与通常的QueryInterface实现相比,它生成的代码更少
  • 它使用NS_ASSERTION测试错误输入,以便在调试构建中立即发现逻辑错误

一些替代方案

NS_IMPL_QUERY_INTERFACE[012] 宏

除了nsISupports外,上面的示例还实现了两个[XP] COM接口。NS_IMPL_QUERY_INTERFACE2宏可以为您编写此函数(尽管我很难推荐宏),例如,

NS_IMPL_QUERY_INTERFACE2(nsMyImplementation, nsIX, nsIY)
                                          // implements |nsMyImplementation::QueryInterface| as above

NS_IMPL_QUERY_INTERFACE1(nsFoo, nsIFoo)   // |nsFoo::QueryInterface| provides |nsIFoo| and |nsISupports|
NS_IMPL_QUERY_INTERFACE0(nsBar)           // |nsBar::QueryInterface| can only provide an |nsISupports|

同样,当仅实现一个附加接口时,可以使用宏NS_IMPL_QUERY_INTERFACE1;当仅实现nsISupports时,可以使用宏NS_IMPL_QUERY_INTERFACE0 。如果使用NS_IMPL_ISUPPORTS*宏,将为您调用这些宏,这些宏提供相应的QueryInterface 实现,以及AddRefRelease

调用继承的 QueryInterface

有时,您只是将一个或两个新接口添加到已经支持许多其他接口的实现中。在这种情况下,在测试了所关心的特定IID之后,您可能希望调用底层实现。这节省了代码空间并降低了复杂性。下面的代码突出显示了这些差异。

class nsMyImplmentation : public nsBaseImplementation, public nsIX, public nsIY { ... };

NS_IMETHODIMP
nsMyImplementation::QueryInterface( REFNSIID aIID, void** aInstancePtr )
    /*
      I just add the interfaces |nsIX| and |nsIY|.
      My base class |nsBaseImplementation| provides all the rest.
    */
  {
    NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");

    if ( !aInstancePtr )
      return NS_ERROR_NULL_POINTER;

    nsISupports* foundInterface;

    if ( aIID.Equals(nsCOMTypeInfo<nsIX>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIX*, this);
    else if ( aIID.Equals(nsCOMTypeInfo<nsIY>::GetIID()) )
      foundInterface = NS_STATIC_CAST(nsIY*, this);
    // Note: Don't check for |nsISupports|; |nsBaseImplementation| will do that for me.
    else
      foundInterface = 0;


    nsresult status;
    if ( !foundInterface )
        // OK, _I_ didn't find an interface.  Maybe my base class can.
      status = nsBaseImplementation::QueryInterface(aIID, &foundInterface);
    else
      {
        NS_ADDREF(foundInterface);
        status = NS_OK;
      }

    *aInstancePtr = foundInterface;
    return status;
  }

请注意,如果 base实现 的 QueryInterface找到了适当的接口,则你的QueryInterface 不能 AddRef 它。这反映在上面的代码中。

这种技术之所以有效,是因为 nsBaseImplementation 已经是一个可以单独使用的完整类。当你从几个完整的类派生时,这种技术不太合适;但如果您对顺序敏感,则仍然可以使用它,例如,

// ...
    nsresult status;
    if ( !foundInterface )
      {
        // OK, ask |nsBase1Imp| first, because I want _it_ to be the one true |nsISupports|.
        status = nsBase1Imp::QueryInterface(aIID, &foundInterface);

        if ( !foundInterface )
          status = nsBase2Imp::QueryInterface(aIID, &foundInterface);

        if ( !foundInterface )
          status = nsBase3Imp::QueryInterface(aIID, &foundInterface);
      }
    else
      {
        NS_ADDREF(foundInterface);
        status = NS_OK;
      }
    // ...

如果您的任何基类参与真正的聚合,那么要把正确的事情做好 即使不是不可能,也是很困难的。您将不能在聚合对象上获得对 QueryInterface 的调用,这可能会返回错误的接口。这也是一个需要特别避免 聚合和复杂的层次结构 的原因。

NS_GET_IID 宏

您可以使用·NS_GET_IID 宏,而不是键入完整的GetIID 表达式。一般来说,我不赞成宏,除非在不同的情况下宏必须扩展为不同的文本,例如,不同的平台,调试与非调试,等等。在这种情况下,宏是必不可少的。在其他情况下,宏可能会帮助一些人,但通常会给其他人带来模糊的问题。它们总是使程序源更加脆弱。在这种情况下,宏只是为了方便起见,所以我不推荐它,但我确实提供了它作为替代。

// ...
    if ( aIID.Equals(NS_GET_IID(nsIX)) )
      foundInterface = NS_STATIC_CAST(nsIX*, this);
    else if ( aIID.Equals(NS_GET_IID(nsIY)) )
      foundInterface = NS_STATIC_CAST(nsIY*, this);

    // ...as many cases as needed...

    else if ( aIID.Equals(NS_GET_IID(nsISupports)) )
    // ...

Thanks

特别感谢 Heikki ToivonenChris WatersonJohn Bandhauer 提供的宝贵反馈,这些反馈极大地改进了这里提供的实现