--- 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 提供的宝贵反馈,这些反馈极大地改进了这里提供的实现