1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
---
title: 实现QueryInterface接口
slug: Implementing_QueryInterface
translation_of: Mozilla/Implementing_QueryInterface
---
<p><font><font>本文档介绍正确地实现</font></font><code>QueryInterface()</code><font><font>的方式 </font></font><font><font>。</font></font></p>
<h3 id="A_reference_implementation_of_QueryInterface" name="A_reference_implementation_of_QueryInterface">QueryInterface的参考实现</h3>
<pre class="eval">NS_IMETHODIMP
nsMyImplementation::QueryInterface( REFNSIID aIID, void** aInstancePtr )
{
NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!");
<span class="comment">// It's a logic error, not a runtime error, to call me without any place to put my answer!</span>
<span class="comment">// ...but that won't matter when someone calls me wrongly in a non-debug build.</span>
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);
<span class="comment">// ...as many cases as needed...</span>
else if ( aIID.Equals(nsCOMTypeInfo<nsISupports>::GetIID()) )
foundInterface = NS_STATIC_CAST(nsISupports*, NS_STATIC_CAST(nsIX*, this));
<span class="comment">// I (may) have multiple |nsISupports| in me,
// so first I cast to a specific base to avoid ambiguity</span>
else
foundInterface = 0;
nsresult status;
if ( !foundInterface )
status = NS_NOINTERFACE;
else
{
NS_ADDREF(foundInterface);
status = NS_OK;
}
*aInstancePtr = foundInterface;
return status;
}
</pre>
<h3 id="What.27s_So_Good_About_It.3F" name="What.27s_So_Good_About_It.3F">有什么好处呢?</h3>
<p>代码优点:</p>
<ul>
<li><font><font>简单明了。</font></font></li>
<li>好的。它有多个 <code>return</code>,但正如预期的那样,主要的 <code>return</code> 位于函数的末尾;并且附加的 <code>return</code>清晰而单独地位于函数的顶部。</li>
<li><font><font>它只有一个</font></font><code>AddRef</code><font><font>。</font></font></li>
<li><code>AddRef</code><font><font>是生成的接口,不是</font></font><code>this</code><font><font>,因此遵循 COM 正确的方式(在聚合中尤其重要)</font></font></li>
<li><font><font>它使用</font></font><code>nsCOMTypeInfo<T>::GetIID()</code><font><font>而不是</font></font><code>kTIID</code><font><font>节省全局声明和全局空间</font></font></li>
<li><font><font>它使用C的</font></font><code>NS_STATIC_CAST </code>定义<code>的</code><code>static_cast</code><font><font>,</font></font><font><font>当您无法真正获得所需的接口时,它将检测错误。</font></font></li>
<li><font><font>它避免了反复使用和分配给</font></font><code>*aInstancePtr</code><font><font>,这些会使编译器在优化时遇到麻烦。</font></font></li>
<li><code>*aInstancePtr</code><font><font>返回错误时</font><font>,将清除结果</font><font>。</font></font></li>
<li><font><font>与通常的</font></font><code>QueryInterface</code><font><font>实现相比,它生成的代码更少</font></font><font><font>。</font></font></li>
<li>它使用<code>NS_ASSERTION</code>测试错误输入,以便在调试构建中立即发现逻辑错误</li>
</ul>
<h3 id="Some_Alternatives" name="Some_Alternatives">一些替代方案</h3>
<h4 id="The_NS_IMPL_QUERY_INTERFACE.5B012.5D_macros" name="The_NS_IMPL_QUERY_INTERFACE.5B012.5D_macros"><code>NS_IMPL_QUERY_INTERFACE</code>[<code>012</code>] 宏</h4>
<p>除了<code>nsISupports</code>外,上面的示例还实现了两个{{ mediawiki.external('XP') }} COM接口。<code>NS_IMPL_QUERY_INTERFACE2</code>宏可以为您编写此函数(尽管我很难推荐宏),例如,</p>
<pre class="eval">NS_IMPL_QUERY_INTERFACE2(nsMyImplementation, nsIX, nsIY)
<span class="comment">// implements |nsMyImplementation::QueryInterface| as above</span>
NS_IMPL_QUERY_INTERFACE1(nsFoo, nsIFoo) <span class="comment">// |nsFoo::QueryInterface| provides |nsIFoo| and |nsISupports|</span>
NS_IMPL_QUERY_INTERFACE0(nsBar) <span class="comment">// |nsBar::QueryInterface| can only provide an |nsISupports|</span>
</pre>
<p>同样,当仅实现一个附加接口时,可以使用宏<code>NS_IMPL_QUERY_INTERFACE1</code>;当仅实现<code>nsISupports</code>时,可以使用宏<code>NS_IMPL_QUERY_INTERFACE0</code> 。如果使用<code>NS_IMPL_ISUPPORTS*</code>宏,将为您调用这些宏,这些宏提供相应的<code>QueryInterface</code> 实现,以及<code>AddRef</code> 和<code>Release</code>。</p>
<h4 id="Calling_an_inherited_QueryInterface" name="Calling_an_inherited_QueryInterface">调用继承的 <code>QueryInterface</code></h4>
<p>有时,您只是将一个或两个新接口添加到已经支持许多其他接口的实现中。在这种情况下,在测试了所关心的特定<code>IID</code>之后,您可能希望调用底层实现。这节省了代码空间并降低了复杂性。下面的代码突出显示了这些差异。</p>
<pre><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;
}</code>
</pre>
<p>请注意,如果 base实现 的 <code>QueryInterface</code>找到了适当的接口,则你的<code>QueryInterface</code> 不能 <code>AddRef</code> 它。这反映在上面的代码中。<br>
<br>
这种技术之所以有效,是因为 <code>nsBaseImplementation</code> 已经是一个可以单独使用的完整类。当你从几个完整的类派生时,这种技术不太合适;但如果您对顺序敏感,则仍然可以使用它,例如,</p>
<pre><code>// ...
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;
}
// ...</code>
</pre>
<p>如果您的任何基类参与真正的聚合,那么要把正确的事情做好 即使不是不可能,也是很困难的。您将不能在聚合对象上获得对 <code>QueryInterface</code> 的调用,这可能会返回错误的接口。这也是一个需要特别避免 聚合和复杂的层次结构 的原因。</p>
<h4 id="The_NS_GET_IID_macro" name="The_NS_GET_IID_macro"><code>NS_GET_IID</code> 宏</h4>
<p>您可以使用·<code>NS_GET_IID</code> 宏,而不是键入完整的<code>GetIID </code>表达式。一般来说,我不赞成宏,除非在不同的情况下宏必须扩展为不同的文本,例如,不同的平台,调试与非调试,等等。在这种情况下,宏是必不可少的。在其他情况下,宏可能会帮助一些人,但通常会给其他人带来模糊的问题。它们总是使程序源更加脆弱。在这种情况下,宏只是为了方便起见,所以我不推荐它,但我确实提供了它作为替代。</p>
<pre><code>// ...
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)) )
// ...</code></pre>
<h3 id="Thanks" name="Thanks">Thanks</h3>
<p>特别感谢 <a href="mailto:heikki@citec.fi">Heikki Toivonen</a>, <a href="mailto:waterson@netscape.com">Chris Waterson</a>和<a href="mailto:jband@netscape.com">John Bandhauer</a> 提供的宝贵反馈,这些反馈极大地改进了这里提供的实现</p>
|