这篇翻译不完整。请帮忙从英语翻译这篇文章。
实现 QueryInterface
This document describes the right way to write QueryInterface()
.
该文档描述如何编写 QueryInterface()。
实现 QueryInterface 的实例。(学习下代码风格。)
A reference implementation of 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; }
What's So Good About It?
代码优点:
- It's clear and simple.
- 简单明了。
- OK. It has more than one
return
, but the primaryreturn
is at the end of the function as expected; and the additionalreturn
is clear and alone at the top of the function. - 有多个return,主return在函数最后,附加的return在函数头。
- It only has one
AddRef
. - 只有一个 AddRef。
- It
AddRef
s the resulting interface, notthis
, thus following the COM-correct way (particularly important in aggregation) - 添加引用的是返回的接口指针,而不是this。
- It uses
nsCOMTypeInfo<T>::GetIID()
instead ofkTIID
thus saving a global declaration and global space - 使用一个方法代替全局变量。
- It uses C 's
static_cast
, viaNS_STATIC_CAST
, which detects errors when you can't really get to the desired interface. - 通过 NS_STATIC_CAST 使用 C 的 static_cast 方法,它可以发现在无法获取所需接口时的错误。
- It avoids repeated uses of and assignments to
*aInstancePtr
, which compilers have trouble optimizing. - 避免对 *aInstancePtr 重复赋值,重复赋值会导致编译器优化比较麻烦。
- It clears the result,
*aInstancePtr
, when returning an error. - 返回错误时,会清空返回值。
- It generates less code than the typical implementation of
QueryInterface
. - 生成的代码少于经典的 QueryInterface 的实现。
- It tests for bad input with an
NS_ASSERTION
, to find logic errors immediately in debug builds. - NS_ASSERTION对输入进行断言测试,在编译中尽快发现逻辑性错误。
Some Alternatives
The NS_IMPL_QUERY_INTERFACE
[012
] macros
The sample above implements two [XP]COM interfaces in addition to nsISupports
. The NS_IMPL_QUERY_INTERFACE2
macro can write this function for you (though it pains me to recommend macros), e.g.,
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|
Similarly, you can use the macro NS_IMPL_QUERY_INTERFACE1
when you implement only one additional interface; and NS_IMPL_QUERY_INTERFACE0
when you only implement nsISupports
. These macros will be invoked for you if you use the NS_IMPL_ISUPPORTS*
macros, which give the corresponding QueryInterface
implementation, plus an AddRef
and a Release
.
Calling an inherited QueryInterface
Sometimes you are just adding one or two new interfaces to an implementation that already supports many other interfaces. In such cases, you'll probably want to call through to the underlying implementation, after you've tested for the particular IID
s that you care about. This saves code-space and reduces complexity. The differences are highlighted in the following code.
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; }
Note that if the base implementation's QueryInterface
finds an appropriate interface, your QueryInterface
must not AddRef
it. This is reflected in the code above.
This technique works because nsBaseImplementation
is already a complete class that could have been used on its own. This technique is less appropriate when you derive from several complete classes; but it can still be used if you are sensitive to the order, e.g.,
// ... 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; } // ...
It will be difficult, if not impossible, to get the right thing to happen if any of your base classes participate in true aggregation. You won't be able to catch calls to QueryInterface
on the aggregated objects, which may then return wrong interfaces. One more reason to avoid aggregation specifically, and complicated hierarchies in general.
The NS_GET_IID
macro
You can use the NS_GET_IID
macro instead of typing out the full GetIID
expression. In general, I disapprove of macros except in cases where the macro must expand to different text in different situations, e.g., different platforms, debugging vs. non-debugging, et al. In such cases macros are indispensible. In other cases macros may help some people but often cloud the issues for others. They always make the program source more fragile. In this case the macro is for convenience only, so I don't recommend it, but I do offer it up as an alternative.
// ... 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
Special thanks to Heikki Toivonen, Chris Waterson, and John Bandhauer for valuable feedback that significantly improved the implementations presented here.
Original Document Information
- Author(s): Scott Collins
- Last Updated Date: May 8, 2003
- Copyright Information: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | Details.