--- title: 实现QueryInterface接口 slug: Implementing_QueryInterface translation_of: Mozilla/Implementing_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节省全局声明和全局空间NS_STATIC_CAST 定义的static_cast,当您无法真正获得所需的接口时,它将检测错误。*aInstancePtr,这些会使编译器在优化时遇到麻烦。*aInstancePtr返回错误时,将清除结果。QueryInterface实现相比,它生成的代码更少。NS_ASSERTION测试错误输入,以便在调试构建中立即发现逻辑错误NS_IMPL_QUERY_INTERFACE[012] 宏除了nsISupports外,上面的示例还实现了两个{{ mediawiki.external('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 实现,以及AddRef 和Release。
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)) )
// ...
特别感谢 Heikki Toivonen, Chris Waterson和John Bandhauer 提供的宝贵反馈,这些反馈极大地改进了这里提供的实现