﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Thinking in C++-文章分类-ATL</title><link>http://www.cppblog.com/yishanhante/category/3741.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 21 May 2008 21:24:54 GMT</lastBuildDate><pubDate>Wed, 21 May 2008 21:24:54 GMT</pubDate><ttl>60</ttl><item><title>COM Objects and Interfaces</title><link>http://www.cppblog.com/yishanhante/articles/19716.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Tue, 13 Mar 2007 05:57:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19716.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19716.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19716.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19716.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19716.html</trackback:ping><description><![CDATA[
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">
						<font size="2">一个对象实现一个接口，他的意思就是该对象使用代码实现了接口的每个方法</font>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<font size="2">
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">并且为这些函数通向</span>
						<span lang="EN-US">com</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">库提供了</span>
						<span lang="EN-US">com</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的二进制指针。然后</span>
						<span lang="EN-US">com</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">使这些函数运行在</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">
						<font size="2">请求了一个指向该接口的任何客户端。</font>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">
						<font size="2">
						</font>
				</span> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 14pt">
								<a href="ms-help://MS.MSDNQTR.2003FEB.2052/com/htm/com_2r5f.htm">
										<span style="TEXT-DECORATION: none; text-underline: none">
												<font color="#0099ff" size="2">Interfaces and Interface Implementations</font>
										</span>
								</a>
								<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /?>
								<o:p>
								</o:p>
						</span>
				</b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt">
				<span lang="EN-US">
						<font size="2">
						</font>
				</span> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt">
				<font size="2">
						<span lang="EN-US">COM </span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在接口的定义和实现上有根本的差别。一个接口实际上是由一组定义了用法的相</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<font size="2">
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">联系的函数原型组成，只是他不能够被实现。这些函数原型就相当于</span>
						<span lang="EN-US">C++</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中含有纯虚拟函数的基类。一个接口定义制定了接口的成员函数、调用方法、返回类型、他们的参数的数量和类型，这些函数要干什么。但是这里并没有与接口实现相关的东西。一个接口的实现就是程序员在一个借口定义上提供的执行相关动作的代码。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<font size="2">
						<span lang="EN-US">
								<span style="mso-tab-count: 1">    </span>
						</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">一个接口的实现就是程序员在一个借口定义上提供的执行相关动作的代码。客户调用完全是决定于接口的定义。</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<font size="2">
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">
						</span>
				</font> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 14pt">
								<a href="ms-help://MS.MSDNQTR.2003FEB.2052/com/htm/com_37w3.htm">
										<span style="TEXT-DECORATION: none; text-underline: none">
												<font color="#0099ff" size="2">Interface Pointers and Interfaces</font>
										</span>
								</a>
						</span>
				</b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 14pt">
								<o:p>
								</o:p>
						</span>
				</b> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt">
				<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">
						<font size="2">接口实现的一个实例，实际上就是一个指向一组方法的指针，即是指指向一个接口的函</font>
				</span>
				<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">
						<font size="2">数表，该函数表引用了该接口所有方法的实现。</font>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt">
				<font size="2">
						<span lang="EN-US">
								<span style="mso-tab-count: 1">    </span>
						</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">每个接口，是一个固定的一组方法的集合，在运行时通过</span>
						<span lang="EN-US">globally unique interface identifier (IID) </span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">来定位。这里，</span>
						<span lang="EN-US">IID</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">是</span>
						<span lang="EN-US">com</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">支持的</span>
						<span lang="EN-US">globally unique identifier (GUID)</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的特殊的实例。这样做就不会产生单一系统上相同名字、接口的多个版本的</span>
						<span lang="EN-US">COM</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">之间的冲突了。</span>
				</font>
		</p>
		<ul type="disc">
				<li class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; tab-stops: list 36.0pt; mso-list: l0 level1 lfo1">
						<font size="2">
								<span style="FONT-FAMILY: 宋体; mso-bidi-font-weight: bold; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">一个<span lang="EN-US">COM接口与C++类是不一样的；</span></span>
								<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
										<o:p>
										</o:p>
								</span>
						</font>
				</li>
				<li class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; tab-stops: list 36.0pt; mso-list: l0 level1 lfo1">
						<font size="2">
								<span style="FONT-FAMILY: 宋体; mso-bidi-font-weight: bold; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">一个<span lang="EN-US">COM接口不是一个对象——他只是简单的关联一组函数，是客户和程序之间通信的二进制标准。只要他提供了指向借口方法的指针，这个对象就可以用任何语言来实现他。；</span></span>
								<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
										<o:p>
										</o:p>
								</span>
						</font>
				</li>
				<li class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; tab-stops: list 36.0pt; mso-list: l0 level1 lfo1">
						<font size="2">
								<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-weight: bold; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">COM接口十强类型的——每个接口有他自己的借口标识符；</span>
								<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
										<o:p>
										</o:p>
								</span>
						</font>
				</li>
		</ul>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt 35.7pt; TEXT-INDENT: -17.85pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; tab-stops: list 36.0pt; mso-list: l0 level1 lfo1" align="left">
				<span lang="EN-US" style="FONT-SIZE: 10pt; FONT-FAMILY: Symbol; mso-bidi-font-size: 12.0pt; mso-font-kerning: 0pt; mso-bidi-font-family: Symbol; mso-fareast-font-family: Symbol">
						<span style="mso-list: Ignore">·<span style="FONT: 7pt 'Times New Roman'"><font size="2">         </font></span></span>
				</span>
				<font size="2">
						<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-weight: bold; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">COM接口的不变性——你不能够用老版本的接口标识符定义新的版本，接口的IID定义的接口合同是明确的、唯一的</span>
						<b>
								<span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">。</span>
						</b>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt 35.7pt; TEXT-INDENT: -17.85pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; tab-stops: list 36.0pt; mso-list: l0 level1 lfo1" align="left">
				<font size="2">
						<b>
								<span style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								</span>
						</b>
						<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								<o:p>
								</o:p>
						</span>
				</font> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 14pt">
								<a href="ms-help://MS.MSDNQTR.2003FEB.2052/com/htm/com_9v6t.htm">
										<span style="TEXT-DECORATION: none; text-underline: none">
												<font color="#0099ff" size="2">IUnknown and Interface Inheritance</font>
										</span>
								</a>
						</span>
				</b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 14pt">
								<o:p>
								</o:p>
						</span>
				</b> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-bottom-alt: auto" align="left">
				<font size="2">
						<span lang="EN-US">
								<span style="mso-tab-count: 1">    </span>
						</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">继承在</span>
						<span lang="EN-US">COM </span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">里并不意味着代码的重用。因为接口没有实现关联，借口继承并意味着代码继承。他的意思仅仅是，一个接口同一个合同关联，就像</span>
						<span lang="EN-US">C++</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的纯虚拟基类的创建和修改样，可以添加方法或者更进一步的加强方法的使用。在</span>
						<span lang="EN-US">COM</span>
						<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">里没有选择性继承。如果一个接口由另一个接口继承的话，他就包含了另一个接口定义的所有的方法。</span>
						<span lang="EN-US">
								<o:p>
								</o:p>
						</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-bottom-alt: auto" align="left">
				<span lang="EN-US">
						<o:p>
								<font size="2"> </font>
						</o:p>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-bottom-alt: auto" align="left">
				<span lang="EN-US" style="FONT-SIZE: 18pt; COLOR: blue">
						<font size="3">
								<strong>
										<font color="#0066ff">Using and Implementing IUnknown<o:p></o:p></font>
								</strong>
						</font>
				</span>
		</p>
		<p style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<o:p>
								<font face="宋体" size="2"> </font>
						</o:p>
				</span>
		</p>
		<p style="MARGIN: 0cm 0cm 0pt">
				<span lang="EN-US">
						<font face="宋体">
								<font size="2">
										<span style="mso-tab-count: 1">    </span>COM 为实现和使用对象和对象的内部通信提供了一个丰富的标准集合。对IUnknown接口的实现和使用的细节，请参见下面主题：<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><a href="ms-help://MS.MSDNQTR.2003FEB.2052/com/htm/com_02b8.htm"><span style="TEXT-DECORATION: none; text-underline: none"><strong>QueryInterface: Navigating in an Object</strong></span></a></span></font>
						</font>
				</span>
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								<font size="2">。 <o:p></o:p></font>
						</span>
				</b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
						<font size="2">
								<font color="#0066ff">
										<span style="mso-tab-count: 1">    </span>
										<o:p>
										</o:p>
								</font>
						</font>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								<a href="ms-help://MS.MSDNQTR.2003FEB.2052/com/htm/com_1j8l.htm">
										<span style="TEXT-DECORATION: none; text-underline: none">
												<font color="#0066ff">Rules for Implementing QueryInterface</font>
										</span>
								</a>
						</span>
				</b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								<font size="2"> <o:p></o:p></font>
						</span>
				</b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<font size="2">
						<span style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">管理实现一个<span lang="EN-US">COM对象的</span></span>
						<span lang="EN-US" style="COLOR: red">
								<a href="ms-help://MS.MSDNQTR.2003FEB.2052/com/htm/cmi_q2z_7fvp.htm">
										<span style="COLOR: red; TEXT-DECORATION: none; text-underline: none">IUnknown::QueryInterface</span>
								</a>
						</span>
						<span style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">方法的三个主要规则：<span lang="EN-US"><o:p></o:p></span></span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
						<font size="2">    1．对象必须要有一个标识符；</font>
				</span>
				<font size="2"> </font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
						<font size="2">    2．一个对象实例的接口集合必须是静态的（<b style="mso-bidi-font-weight: normal">static</b>）；</font>
				</span>
				<font size="2"> </font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<font size="2">
						<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">    3．在对象中从任何一个其他的接口查询此接口都应该成功。</span>
						<span lang="EN-US" style="FONT-FAMILY: 宋体; mso-bidi-font-size: 10.5pt; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								<o:p> </o:p>
						</span>
				</font>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<b style="mso-bidi-font-weight: normal">
						<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
								<font size="2">
								</font>
						</span>
				</b> </p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<span lang="EN-US" style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">
						<span style="TEXT-DECORATION: none; text-underline: none">
								<font size="2">通过引用计数来管理对象的生命周期</font>
						</span>
				</span>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan" align="left">
				<font size="2">
						<span lang="EN-US" style="COLOR: blue; mso-bidi-font-size: 10.5pt">
								<span style="mso-tab-count: 1">    </span>
						</span>
						<span style="COLOR: blue; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-size: 10.5pt">使用</span>
						<span lang="EN-US" style="COLOR: blue; mso-bidi-font-size: 10.5pt">AddRef</span>
						<span style="COLOR: blue; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-size: 10.5pt">（）</span>
						<span style="COLOR: blue; mso-bidi-font-size: 10.5pt">    //增加引用</span> <br /><font color="#0000ff">                <span lang="EN-US" style="COLOR: blue; mso-bidi-font-size: 10.5pt">Realase</span><span style="COLOR: blue; FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'; mso-bidi-font-size: 10.5pt">（）   //减少引用</span><span lang="EN-US" style="COLOR: blue; mso-bidi-font-size: 10.5pt"><o:p></o:p></span></font></font>
		</p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19716.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-13 13:57 <a href="http://www.cppblog.com/yishanhante/articles/19716.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ATL3.0中的窗口类（3）</title><link>http://www.cppblog.com/yishanhante/articles/19690.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Tue, 13 Mar 2007 02:08:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19690.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19690.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19690.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19690.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19690.html</trackback:ping><description><![CDATA[
		<strong>ATL中的对话框类：</strong>
		<br />现在我们对ATL中的窗口类有了一定的了解，接着我们来学习对话框类。在你的项目中，可能有很多对话框资源，从最简单的“关于”模式对话框到复杂的满是控件的非模式对话框。ATL提供了CSimpleDialog类和CDialogImpl类来简化我们使用对话框资源的过程。 <br /><br />CSimpleDialog <br />CSimpleDialog是一个从模版创建模式对话框的类。它提供了一些标准按纽（如OK和CANCEL）的处理过程。你可以将CSimpleDialog想象成是一种消息对话框（Message Box），不同的是你可以在对话框编辑器中编辑它的外观。 <br /><br />要显示这样一个对话框，比如当你点击“帮助”菜单中的“关于”菜单项时显示关于对话框，你需要在主窗口类中添加如下的消息映射： <pre>BEGIN_MSG_MAP( CMyMainWindow )
   COMMAND_ID_HANDLER( ID_HELP_ABOUT, onHelpAbout )
   ...

LRESULT onHelpAbout( WORD, WORD, HWND, BOOL&amp; )
{
   CSimpleDialog&lt;IDD_DIALOG1&gt; dlg;
   int ret = dlg.DoModal();
   return 0;
}</pre>我们可以看到对话框资源的ID（IDD_DIALOG1）被作为一个模版参数传递给CSimpleDialog类，DoModal方法显示对话框。当用户点击OK按钮时，CSimpleDialog类关闭对话框并返回按钮的ID。（CSimpleDialog类实现了对按钮IDOK，IDCANCEL，IDABORT，IDRETRY，IDIGNORE，IDYES和IDNO的响应。） <br /><br />CDialogImpl <br />CSimpleDialog类只能够处理简单的模式对话框，对于更加复杂的对话框或者模式对话框，就要用到CDialogImpl类。（其实CSimpleDialog是CDialogImpl中的一种特例。） <br /><br />如果我们需要实现一个非模式对话框，我们必须从CDialogImpl派生出一个新类，并将新类的类名作为模板参数传递给CDialogImpl类，就象前面的CWindowImpl一样： <pre>class CMyModelessDialog: public CDialogImpl<cmymodelessdialog>
{
</cmymodelessdialog></pre>和CSimpleDialog不同，我们不需要将对话框资源的ID作为模板参数传递给它，但是我们必须将这个类和对话框资源联系起来，我们通过在类中定义一个枚举变量实现： <pre>public:
   enum { IDD = IDD_DIALOG1 };
</pre>然后定义消息映射表： <pre>BEGIN_MSG_MAP( CMyDialog )
   MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
   MESSAGE_HANDLER( WM_CLOSE, OnClose )
   ...
END_MSG_MAP()
</pre>响应函数的定义和前面的一样，但是有一点需要注意，如果你实现的是一个非模式对话框，那么在WM_CLOSE消息的响应函数中必须调用DestroyWindow：<pre>LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL&amp; )
{
   DestroyWindow();
   return 0;
}
...
}; // CMyModelessDialog
</pre>要在屏幕上创建这样一个对话框，需要创建这个类的一个实例并调用Create方法： <pre>CMyModelessDialog dlg;
dlg.Create( wndParent );
</pre>如果对话框资源没有选中WS_VISIBLE属性，我们需要这样让对话框显示出来： <pre>dlg.ShowWindow( SW_SHOW );</pre>下面的例子有一个非模式的对话框可以接受用户输入的字符串，然后在主窗口中显示这个字符串。对话框中有一个编辑框控件和一个按钮；当按钮被点击时，对话框调用它所属窗口的DoSomething方法对编辑框中的字符串进行处理，它所属的窗口是一个超类化的列表框控件，DoSomething方法的功能是将字符串添加到列表框中。 <pre>#include "atlbase.h"
CComModule _Module;
#include "atlwin.h"
#include "resource.h"

class CMyWindow: public CWindowImpl&lt;CMyWindow&gt;
{
public:
   DECLARE_WND_SUPERCLASS( "MyWindow", "listbox" )

   BEGIN_MSG_MAP( CMyWindow )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
   END_MSG_MAP()

   LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      PostQuitMessage( 0 );
      return 0;
   }

   void DoSomething( LPCTSTR s )
   {
      SendMessage( LB_ADDSTRING, 0, reinterpret_cast&lt;LPARAM&gt;(s) );
   }
};

class CMyDialog: public CDialogImpl&lt;CMyDialog&gt;
{
public:
   enum { IDD = IDD_DIALOG1 };
   BEGIN_MSG_MAP( CMyDialog )
      COMMAND_ID_HANDLER( IDC_BUTTON1, OnButton )
      MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
      MESSAGE_HANDLER( WM_CLOSE, OnClose )
   END_MSG_MAP()

   LRESULT OnButton(WORD, WORD, HWND, BOOL&amp;)
   {
      char buf[100];
      m_ed.GetWindowText( buf, 100 );
      m_owner.DoSomething( buf );
      return 0;
   }

   LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      m_owner.Attach( GetParent() );
      CenterWindow( m_owner );
      m_ed = GetDlgItem( IDC_EDIT1 );
      return TRUE;
   }

   LRESULT OnClose( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      DestroyWindow();
      m_owner.Detach();
      return 0;
   }

   CMyWindow m_owner;
   CWindow m_ed;
};

CMyDialog dlg;

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
   _Module.Init( NULL, hInstance );
   CMyWindow win;
   win.Create( NULL, CWindow::rcDefault, _T("modeless dialog test"),
      WS_OVERLAPPEDWINDOW|WS_VISIBLE );
   dlg.Create( win );
   dlg.ShowWindow( SW_SHOW );

   MSG msg;
   while( GetMessage( &amp;msg, NULL, 0, 0 ) ){
      if( !IsWindow(dlg) || !dlg.IsDialogMessage( &amp;msg ) ){
         DispatchMessage( &amp;msg );
      }
   }
   _Module.Term();
   return 0;
}
</pre><strong><a id="A13" name="A13"></a>指定窗口类的信息：</strong><br />这篇文章的大部分内容都是在讲述怎样处理窗口类的行为——窗口怎样响应消息。在行为之外，一个窗口类还具有一些其它的重要的属性，比如样式、类名、背景颜色和指针等等。这一节介绍怎样使用ATL中提供的宏来指定这些属性。 <br /><br />使用Window Traits指定窗口的样式 <br />到目前为止，所有例子中的窗口样式都是在调用Create方法时指定的： <pre>CMyWindow wnd;
wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
   WS_OVERLAPPEDWINDOW|WS_VISIBLE );
</pre>如果你不指定任何样式和扩展样式，ATL将使用默认的样式；这些默认的样式是作为窗口的特征定义的，默认特征是CControlWinTraits，定义如下：<pre>typedef CWinTraits&lt;WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN
   |WS_CLIPSIBLINGS, 0&gt; CControlWinTraits;
</pre>CWinTraits是一个模板类，它需要2个参数：窗口样式、扩展窗口样式。 <br /><br />在CWindowImpl的定义中，CControlWinTraits作为默认的模板参数传递给CWindowImpl： <pre>template &lt;class T,
          class TBase = CWindow,
          class TWinTraits = CControlWinTraits&gt;
class CWindowImpl : public ...
</pre>所以在默认情况下，从CWindowImpl派生的窗口都具有可视、子窗口、裁剪兄弟窗口、裁减子窗口的属性。 <br /><br />我们也能定义自己的窗口特征： <pre>typedef CWinTraits&lt;WS_OVERLAPPEDWINDOW|WS_VISIBLE,0&gt;
   MyTraits;
</pre>然后，从CWindowImpl派生一个窗口类，指定自己的窗口特征： <pre>class CMyWindow: public CWindowImpl&lt;CMyWindow,CWindow,MyTraits&gt;
{...};
</pre>或者象下面这样更加直接： <pre>class CMyWindow: public CWindowImpl&lt;
   CMyWindow,
   CWindow,
   CWinTraits&lt;WS_OVERLAPPEDWINDOW|WS_VISIBLE,0&gt; 
&gt;
{...};</pre>注意，我们必须提供全部的三个模板参数：派生类，基类（CWindow）和特征类。 <br /><br />CMyWindow窗口现在具有的默认的样式为“可见的弹出窗口”，所以我们可以在Create方法中省略样式参数： <pre>CMyWindow wnd;
wnd.Create( NULL, CWindow::rcDefault, _T("Hello") );
// style: WS_OVERLAPPEDWINDOW|WS_VISIBLE
</pre>我们也可以重写窗口特征：<pre>ovwnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
   WS_OVERLAPPEDWINDOW ); // not visible
</pre>窗口特征也可以包含扩展样式：<pre>class CClientWindow: public CWindowImpl&lt;CClientWindow, CWindow,
   CWinTraits&lt; WS_OVERLAPPEDWINDOW|WS_VISIBLE, WS_EX_CLIENTEDGE &gt; &gt;
{...};
</pre>DECLARE_WND_CLASS <br />使用DECLARE_WND_CLASS宏可以指定窗口的类名： <pre>DECLARE_WND_CLASS("my window class");</pre>这等价于：<pre>DECLARE_WND_CLASS_EX(
   "my window class",
   CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, // default style
   COLOR_WINDOW                      // default color
);
</pre>DECLARE_WND_CLASS_EX <br />使用DECLARE_WND_CLASS_EX宏可以指定窗口类名、样式和背景颜色： <pre>class CMyWindow: public CWindowImpl&lt;CMyWindow&gt;
{
public:
   DECLARE_WND_CLASS_EX(
      "my window class",       // class name
      CS_HREDRAW|CS_VREDRAW,   // class style
      COLOR_WINDOW             // background color
   );
   BEGIN_MSG_MAP(CMyWindow)
      ...
</pre>所谓的窗口类名是指注册的窗口类的名字，如果我们不指定窗口类名，ATL将自动生成一个，但是当我们使用Spy++之类的工具的时候，你将会发现我们自己取的类名比"ATL:00424bd0"之类的名字要有用得多。 <br /><br />类样式是按照按位或组合的。 <br /><br />背景颜色必须是标准系统颜色之一。 <br /><br />CWndClassInfo <br />我们也可以定义超出DECLARE_WND_宏能力之外的窗口类。如果你看看DECLARE_WND_CLASS的定义你就会发现它定义了一个CWndClassInfo结构，并且一个函数返回这种结构类型的值： <pre>#define DECLARE_WND_CLASS(WndClassName) \
static CWndClassInfo&amp; GetWndClassInfo() \
{ \
   static CWndClassInfo wc = \
   { \
      { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, \
        StartWindowProc, \
        0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, \
        WndClassName, NULL }, \
      NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
   }; \
   return wc; \
}
</pre>CWndClassInfo结构提供了更灵活的自定义的可能，它是这样定义的： <pre>struct CWndClassInfo
{
   struct WNDCLASSEX
   {
      UINT        cbSize;
      UINT        style;
      WNDPROC     lpfnWndProc;
      int         cbClsExtra;
      int         cbWndExtra;
      HINSTANCE   hInstance;
      HICON       hIcon;
      HCURSOR     hCursor;
      HBRUSH      hbrBackground;
      LPCSTR      lpszMenuName;
      LPCSTR      lpszClassName;
      HICON       hIconSm;
   } m_wc;
   LPCSTR m_lpszOrigName;
   WNDPROC pWndProc;
   LPCSTR m_lpszCursorID;
   BOOL m_bSystemCursor;
   ATOM m_atom;
   CHAR m_szAutoName[13];
   ATOM Register(WNDPROC* p);
};
</pre>例如，要指定一个窗口的指针，我们可以将m_lpszCursorID设置为指针的名字，如果它是一个系统指针，将m_bSystemCursor设置为TRUE，否则设置为FALSE。注意DECLARE_WND_CLASS宏是怎样将这两个成员变量分别设置为IDC_ARROW 和 TRUE的。既然DECLARE_WND_宏不能让我们改写这些默认的值，我们可以这样做： <pre>class CMyWindow: public CWindowImpl&lt;CMyWindow&gt;
{
public:
   static CWndClassInfo&amp; GetWndClassInfo()
   {
      // a manual DECLARE_WND_CLASS macro expansion
      // modified to specify an application-defined cursor:
      static CWndClassInfo wc =
      {
         { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, 
            StartWindowProc,
            0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, 
            "MyWindow", NULL },
         NULL, NULL, MAKEINTRESOURCE(IDC_CURSOR1), FALSE, 0, _T("")
      };
      return wc;
   }
   ...
</pre><strong><a name="A14"></a>结论：</strong><br />ATL提供了一种简单的、雅致的并且功能强大的窗口编程模式。在那些方便的封装好了的函数、消息映射和宏之外，还有一些技术诸如链接、窗口的子类化和超类化、被包含的窗口和消息反射等也使得设计和实现窗口和对话框非常灵活。或许ATL给人最深的印象就是：功能强大、灵活性好，但是不会占用太多的内存和系统开销。<img src ="http://www.cppblog.com/yishanhante/aggbug/19690.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-13 10:08 <a href="http://www.cppblog.com/yishanhante/articles/19690.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ATL3.0中的窗口类（2）</title><link>http://www.cppblog.com/yishanhante/articles/19689.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Tue, 13 Mar 2007 02:07:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19689.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19689.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19689.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19689.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19689.html</trackback:ping><description><![CDATA[
		<div>
				<a id="A10" name="A10">
				</a>
				<strong>被包含的窗口：</strong>
				<br />一个被包含的窗口是一个不响应任何消息的窗口，它将收到的所有消息重新发送到另外一个窗口的消息映射，这个另外的窗口就是它的容器窗口。通常情况下，被包含的窗口是它的容器窗口的子窗口，但情况并不是总是这样。容器窗口并不是必须等同于父窗口，包含与被包含的关系取决于C++类，被包含的窗口是容器窗口类的一个数据成员，而父窗口和子窗口的关系体现在屏幕上，它们的关系是创建窗口时确定的。 <br /><br />一个被包含的窗口建立在已注册的窗口类的基础之上，比如编辑框控件。如果一个编辑框被包含，那么发送到它的消息实际上被它的容器窗口的消息映射处理。使用这种方法，可以改变编辑框控件的标准行为。这有点类似于子类化但是不需要定义新类来子类化控件。和前面那个定义CnoNumEdit类响应WM_CHAR消息的例子相比，处理WM_CHAR消息的容器窗口类看起来如下： <pre>class CMyWindow: public CWindowImpl<cmywindow>
{
   CContainedWindow m_contained;
public:
   CMyWindow(): m_contained( _T("edit"), this, 99 )
   {
   }
   ...
</cmywindow></pre>CmyWindow是一个容器窗口类，它的构造函数对CcontainedWindow类型的成员做这样的初始化：被包含的窗口是编辑框，发送它的消息到“this”（它的父窗口），使用可选消息映射表99。<pre>BEGIN_MSG_MAP( CMyWindow )
   MESSAGE_HANDLER( WM_CREATE, OnCreate )
   MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
ALT_MSG_MAP( 99 ) // contained window''s messages come here...
   MESSAGE_HANDLER( WM_CHAR, OnChar )
END_MSG_MAP()
</pre>当父窗口被创建的时候，被包含的窗口也被创建（在WM_CREATE消息的响应函数中）。因为被包含的控件是以编辑框为基础的，所以它在屏幕上看起来象一个编辑框： <pre>LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL&amp; )
{
   RECT rc = { 10, 10, 200, 35 };
   m_contained.Create( *this, rc, _T("non-numeric edit"),
      WS_CHILD|WS_VISIBLE|WS_BORDER, 0, 666 );
   return 0;
}
</pre>在这个例子中，容器窗口同时也是被包含窗口的父窗口。 <br /><br />当被包含的窗口收到WM_CHAR消息时，容器窗口的OnChar成员函数被调用。这个函数和前面的CnoNumEdit例子中的相同，但是在这个例子中，它时容器类的成员函数。 <pre>LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL&amp; bHandled )
   {
   TCHAR ch = wParam;
   if( _T(''0'') &lt;= ch &amp;&amp; ch &lt;= _T(''9'') )
      MessageBeep( 0 );
   else
      bHandled = FALSE;
   return 0;
   }

LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
   PostQuitMessage( 0 );
   return 0;
   }
};
</pre>我们同样也可以用被包含的窗口来子类化对话框中已经存在的控件，和正规的子类化不同，被子类化的窗口的消息时被容器窗口捕获的。在下面的例子中，一个对话框子类化了一个编辑框控件，把它转化成了被包含的窗口；那个对话框（容器）捕获WM_CHAR消息并忽略掉数字字符，然后在发送到编辑框控件。（CdialogImpl在ATL中的对话框类一节讲述。） <pre>class CMyDialog: public CDialogImpl&lt;CMyDialog&gt;
{
public:
   enum { IDD = IDD_DIALOG1 };
// contained window is an edit control:
   CMyDialog(): m_contained( "edit", this, 123 )
   {
   }

   BEGIN_MSG_MAP( CMyDialog )
      MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
   ALT_MSG_MAP( 123 ) // contained window''s messages come here...
      MESSAGE_HANDLER( WM_CHAR, OnChar )
   END_MSG_MAP()

   LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL&amp; bHandled )
   {
   // when the dialog box is created, subclass its edit control:
      m_contained.SubclassWindow( GetDlgItem(IDC_EDIT1) );
      bHandled = FALSE;
      return 0;
   }

   LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL&amp; bHandled )
   {
      TCHAR ch = wParam;
      if( _T(''0'') &lt;= ch &amp;&amp; ch &lt;= _T(''9'') )
         MessageBeep( 0 );
      else
         bHandled = FALSE;
      return 0;
   }

   CContainedWindow m_contained;
};
</pre><strong><a id="A11" name="A11"></a>消息反射：</strong><br />前面讲述了一些扩展窗口功能的方法，这些方法是通过使窗口响应发往窗口的消息实现的。和前面的方法相反，消息反射是使窗口能够响应从它们自己发出的消息。 <br /><br />当用户和控件交互的时候，控件通常使发送一个WM_COMMAND或者WM_NOTIFY消息给它的父窗口；然后父窗口做出响应，比如： <pre>class CParentWindow: CWindowImpl&lt;CParentWindow&gt;
{
   // 假设这个窗口有一个按钮型的子窗口，
   // 并且其 ID 为 ID_BUTTON
   BEGIN_MSG_MAP( CParentWindow )
      COMMAND_ID_HANDLER( ID_BUTTON, OnButton )
      MESSAGE_HANDLER( WM_CTLCOLORBUTTON, OnColorButton )
      ...
</pre>当按钮被按下的时候，它发送一个命令消息给父窗口，然后CParentWindow::OnButton被调用。同理，当按钮需要被绘制的时候，它发送WM_CTLCOLORBUTTON消息给父窗口，CParentWindow::OnColorButton响应这个消息，它使用特定的画刷绘制控件。 <br /><br />某些情况下，让控件自己响应它发送出去的消息比让父窗口响应要好得多。ATL提供了消息反射的机制：当控件向父窗口发送消息的时候，父窗口能够将消息反射给控件。 <pre>class CParentWindow: CWindowImpl<cparentwindow>
{
   BEGIN_MSG_MAP( CParentWindow )
      MESSAGE_HANDLER( WM_CREATE, OnCreate )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
      ...other messages that CParentWindow will handle...
      REFLECT_NOTIFICATIONS()
   END_MSG_MAP()
   ...
</cparentwindow></pre>当父窗口收到一个消息，先查找它的消息映射表，如果没有和这个消息相匹配的入口，则REFLECT_NOTIFICATIONS宏使得该消息被反射给发送这个消息的控件。控件可以提供响应反射消息的处理函数，如下： <pre>class CHandlesItsOwnMessages: CWindowImpl&lt;CHandlesItsOwnMessage&gt;
{
public:
   DECLARE_WND_SUPERCLASS( _T("Superbutton"), _T("button") )
   BEGIN_MSG_MAP( CHandlesItsOwnMessage )
      MESSAGE_HANDLER( OCM_COMMAND, OnCommand )
      MESSAGE_HANDLER( OCM_CTLCOLORBUTTON, OnColorButton )
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()
   ...
</pre>注意，反射消息的消息标志以OCM_开头，而不是WM_。这可以让你区分这个消息究竟是否是被反射回来的。 <br /><br />这个控件要么是这个类的实例，要么是一个被子类化的按钮控件。例如： <pre>// in CParentWindow:
   CHandlesItsOwnMessages m_button;
   LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      RECT rc; // initialize appropriately
      m_button.Create( *this, rc, _T("click me"), WS_CHILD|WS_VISIBLE );
      ...
</pre>或者，如果这个按钮控件是已存在的（例如，父窗口是一个对话框）：<pre>m_button.SubclassWindow( GetDlgItem(ID_BUTTON) );
</pre>下面的例子定义了一个CstaticLink类，它是一个Static控件，当点击它的时候，将打开一个指定的网页。所有从CstaticLink发送出去的消息都被它的父窗口反射回来（在这个例子中，用到对话框，请看ATL中的对话框类这一节）。除了响应反射回的命令消息，CstaticLink还处理反射回的WM_CTLCOLORSTATIC消息以便它能够让自己在点击前和点击后显示不同的颜色。 <pre>#include "stdafx.h"
#include "resource.h"

CComModule _Module;

class CStaticLink : public CWindowImpl&lt;CStaticLink&gt; {
/*
   Based on CStaticLink by Paul DiLascia, C++ Q&amp;A, Microsoft Systems
   Journal 12/1997.
   Turns static controls into clickable "links" -- when the control is
   clicked, the file/program/webpage named in the control''s text (or
   set by SetLinkText()) is opened via ShellExecute().  Static control
   can be either text or graphic (bitmap, icon, etc.).
*/
public:
   DECLARE_WND_SUPERCLASS( _T("StaticLink"), _T("Static") )

   CStaticLink() :
      m_colorUnvisited( RGB(0,0,255) ),
      m_colorVisited( RGB(128,0,128) ),
      m_bVisited( FALSE ),
      m_hFont( NULL )
   {
   }

   void SetLinkText( LPCTSTR szLink ) {
      USES_CONVERSION;
      m_bstrLink = T2OLE( szLink );
   }

   BEGIN_MSG_MAP(CStaticLink)
      // uses message reflection: WM_* comes back as OCM_*
      MESSAGE_HANDLER( OCM_COMMAND, OnCommand )
      MESSAGE_HANDLER( OCM_CTLCOLORSTATIC, OnCtlColor )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) // not a reflected message
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL&amp; ) {
      if( m_hFont ) DeleteObject( m_hFont );
      return 0;
   }

   LRESULT OnCommand( UINT, WPARAM wParam, LPARAM, BOOL&amp; ) {
      USES_CONVERSION;
      int code = HIWORD( wParam );
      if( code == STN_CLICKED || code == STN_DBLCLK ){
         if( m_bstrLink.Length() == 0 ){
            GetWindowText( &amp;m_bstrLink );
         }
         if( (int)ShellExecute( *this, _T("open"),
            OLE2T(m_bstrLink), NULL, NULL, SW_SHOWNORMAL ) &gt; 32 ){
            m_bVisited = TRUE;   // return codes &gt; 32 =&gt; success
            Invalidate();
         }else{
            MessageBeep( 0 );
            ATLTRACE( _T("Error: CStaticLink couldn''t open file") );
         }
      }
      return 0;
   }

   LRESULT OnCtlColor( UINT, WPARAM wParam, LPARAM, BOOL&amp; ) {
      // notify bit must be set to get STN_* notifications
      ModifyStyle( 0, SS_NOTIFY );
      HBRUSH hBr = NULL;
      if( (GetStyle() &amp; 0xff) &lt;= SS_RIGHT ){
         // it''s a text control: set up font and colors
         if( !m_hFont ){
            LOGFONT lf;
            GetObject( GetFont(), sizeof(lf), &amp;lf );
            lf.lfUnderline = TRUE;
            m_hFont = CreateFontIndirect( &amp;lf );
         }
         HDC hDC = (HDC)wParam;
         SelectObject( hDC, m_hFont );
         SetTextColor( hDC, m_bVisited ? m_colorVisited
                                       : m_colorUnvisited );
         SetBkMode( hDC, TRANSPARENT );
         hBr = (HBRUSH)GetStockObject( HOLLOW_BRUSH );
      }
      return (LRESULT)hBr;
   }

private:
   COLORREF m_colorUnvisited;
   COLORREF m_colorVisited;
   BOOL m_bVisited;
   HFONT m_hFont;
   CComBSTR m_bstrLink;
}; // CStaticLink

class CReflectDlg : public CDialogImpl&lt;CReflectDlg&gt; {
public:
   enum { IDD = IDD_DIALOG1 };
   
   BEGIN_MSG_MAP(CReflectDlg)
      COMMAND_RANGE_HANDLER( IDOK, IDCANCEL, OnClose )
      MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
      REFLECT_NOTIFICATIONS()      // reflect messages back to static links
   END_MSG_MAP()
      
   LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&amp;)
   {
      CenterWindow( GetParent() );
      // a textual static control:
      s1.SubclassWindow( GetDlgItem(IDS_TEST1) );
      // a static control displaying an icon
      s2.SubclassWindow( GetDlgItem(IDS_TEST2) );
      // set the icon''s link
      s2.SetLinkText( _T("http://www.microsoft.com") );
      return 1;
   }
   
   LRESULT OnClose(UINT, WPARAM wID, HWND, BOOL&amp; )
   {
      ::EndDialog( m_hWnd, wID );
      return 0;
   }
private:
   CStaticLink s1, s2;
}; // CReflectDlg

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
   _Module.Init( NULL, hInstance );

   CReflectDlg dlg;
   dlg.DoModal();

   _Module.Term();
   return 0;
}
</pre><strong><a id="A12" name="A12"></a></strong></div>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19689.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-13 10:07 <a href="http://www.cppblog.com/yishanhante/articles/19689.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ATL3.0中的窗口类</title><link>http://www.cppblog.com/yishanhante/articles/19687.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Tue, 13 Mar 2007 01:52:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19687.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19687.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19687.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19687.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19687.html</trackback:ping><description><![CDATA[
		<div>摘要：讨论Active Template Library (ATL) 3.0中的一些类，这些类围绕着Windows API建立了一个面向对象的编程框架，使用这个框架，可以简化Microsoft&amp;reg; Windows&amp;reg;编程并且只需要很少的系统开销。内容包括：考察对窗口做了简单封装的CWindow类；使用CWindowImpl进行消息处理和消息映射；使用ATL中的对话框类以及扩展现有窗口类的功能的方法。 <br /><br /><strong><a id="A1" name="A1"></a>简介：</strong><br />虽然Active Template Library (ATL)主要是为了支持COM开发而设计的，但它确实包含了很多可用于窗口设计的类。这些窗口类和ATL中的其它类一样，都是基于模版的，并且只需要花费很少系统开销。这篇文章就向我们演示了使用ATL创建窗口和对话框并进行消息处理的基本方法。 <br />这篇文章假设读者熟悉C++语言和Windows程序设计；但是并不一定要求读者具有COM方面的知识。 <br /><br /><strong><a id="A2" name="A2"></a>CWindow:</strong><br />在ATL窗口类中，CWindow是最基本的。这个类对Windows API进行了面向对象的包装，它封装了一个窗口句柄，并提供一些成员函数来操作它，这些函数包装了相应的Windows API。 <br /><br />标准的Windows程序设计看起来象这样： <br /><pre>HWND hWnd = ::CreateWindow( "button", "Click me", 
WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
::ShowWindow( hWnd, nCmdShow );
::UpdateWindow( hWnd );
</pre>使用ATL中的CWindow类后，等效代码如下：<pre>CWindow win;
win.Create( "button", NULL, CWindow::rcDefault, "Click me",
WS_CHILD );
win.ShowWindow( nCmdShow );
win.UpdateWindow();
</pre>我们应该在我们的大脑中我们应该保持这样一个概念：ATL的窗口对象与Windows系统中的窗口是不同的。Windows系统中的窗口指的是操作系统中维持的一块数据，操作系统靠这块数据来操作屏幕上的一块区域。而一个ATL窗口对象，是CWindow类的一个实例，它是一个C++对象，它的内部没有保存任何有关屏幕区域或者窗口数据结构的内容，只保存了一个窗口的句柄，这个句柄保存在它的数据成员m_hWnd中，CWindow对象和它在屏幕上显示出来的窗口就是靠这个句柄联系起来的。<br />理解了ATL中的窗口对象和Windows系统中窗口的区别，就更加容易理解CWindow对象的构造与窗口的创建是两个分开的过程。我们再看看前面的代码，就会发现，首先是一个CWindow对象被构造： <pre>CWindow win;</pre>然后创建它的窗口：<pre>win.Create( "button", NULL, CWindow::rcDefault, "Click me",
WS_CHILD );
</pre>我们也可以构造一个CWindow对象，然后把它和一个已经存在的窗口关联起来，这样我们就可以通过CWindow类的成员函数来操作这个已经存在的窗口。这种方法非常有用，因为CWindow类提供的函数都是封装好了的，用起来很方便，比如CWindow类中的CenterWindow, GetDescendantWindow等函数用起来就比直接使用Windows API方便得多。 <pre>HWND hWnd = CreateWindow( szWndClass, "Main window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
// 下面的方法中可以任选一种：
//      CWindow win( hWnd );      // 通过构造函数关联
// 或
//      CWindow win;
//      win = hWnd;               // 通过赋值操作符关联
// 或
//      CWindow win;
//      win.Attach( hWnd );      // 使用Attach()方法关联
win.CenterWindow();      // 现在可以使用win对象来代替hWnd进行操作
win.ShowWindow( nCmdShow );
win.UpdateWindow();
</pre>CWindow类也提供了一个HWND操作符，可以把CWindow类的对象转化为窗口句柄，这样，任何要求使用HWND的地方都可以使用CWindow类的对象代替： <pre>::ShowWindow( win, nCmdShow );      // 此API函数本来要求HWND类型的参数</pre>CWindow类使得对窗口的操作更简单，而且不会增加系统开销——它经过编译和优化后的代码与使用纯API编程的代码是等价的。<br /><br />不幸的是，CWindow类不能让我们自己决定窗口如何响应消息。当然，我们可以使用CWindow类提供的方法来使一个窗口居中或隐藏，甚至可以向一个窗口发送消息，但是当窗口收到消息后怎么处理则取决于创建这个窗口时使用的窗口类，如果我们是创建的是”button”类的窗口，那么它的表现就象个按钮，如果用”listbox”类创建，那它就具有跟列表框相同的行为，使用CWindow类我们没有办法改变这点。幸好，ATL为我们提供了另外一个类CWindowImpl，它允许我们指定窗口的新行为。 <br /><br /><strong><a id="A3" name="A3"></a>CWindowImpl:</strong><br />CWindowImpl类是从CWindow类派生的，所以我们依然可以使用CWindow类中的成员函数，但是CWindowImpl类的功能更强大，它允许我们指定窗口怎样处理消息。在传统的窗口编程中，如果我们要处理窗口消息，我们必须使用窗口函数；但是使用ATL，我们只需要在我们的ATL窗口类中定义一个消息映射。 <br /><br />首先，从CWindowImpl类派生自己的窗口类，如下： <pre>class CMyWindow : public CWindowImpl<cmywindow>
{
</cmywindow></pre>注意，我们自己的类名必须作为一个模版参数传递给CWindowImpl类。 <br /><br />然后在类的定义里面定义如下的消息映射： <pre>BEGIN_MSG_MAP(CMyWindow)
   MESSAGE_HANDLER(WM_PAINT,OnPaint)
   MESSAGE_HANDLER(WM_CREATE,OnCreate)
   MESSAGE_HANDLER(WM_DESTROY,OnDestroy)
END_MSG_MAP()
</pre>下面这句<pre>MESSAGE_HANDLER(WM_PAINT,OnPaint)</pre>的意思是，当WM_PAINT消息到达时，将调用CMyWindow::OnPaint成员函数。 <br /><br />最后就是定义处理消息的函数了，如下： <pre>LRESULT OnPaint(
   UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled )
{ ... 
}
LRESULT OnCreate(
   UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled )
{ ...
}
LRESULT OnDestroy(
   UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled )
{ ... 
}
}; // CmyWindow
</pre>这些函数中的参数意义为：第一个是消息ID，中间的两个参数的意义取决于消息类型，第四个参数是一个标志，用它来决定这个消息是已经处理完了还是需要进一步的处理。关于这些参数，我们在Message Map小结有更详细的讨论。 <br /><br />当窗口收到一个消息，它将从消息映射表的顶部开始查找匹配的消息处理函数，因此把最常用的消息放在消息映射表的前面是个不错的注意。如果没有找到匹配的消息处理函数，则这个消息被发送到默认的窗口过程进行处理。 <br /><br />ATL的消息映射表封装了Windows的消息处理过程，它比传统的窗口函数中的大量switch分支或者if语句看起来更加直观。 <br /><br />要创建一个基于CWindowImpl派生类的窗口，请调用CWindowImpl类的Create方法： <pre>CMyWindow wnd;      // 构造一个 CMyWindow 类的对象
wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
   WS_OVERLAPPEDWINDOW|WS_VISIBLE );
</pre>注意，CWindowImpl类的Create方法与CWindow类的Create方法略有不同，在CWindow类的Create中，我们必须指定一个注册了的窗口类，但是CWindowImpl则不同，它创建一个新的窗口类，因此，不需要为它指定窗口类。 <br /><br /><strong><a id="A4" name="A4"></a>一个简单而完整的示例：<br /></strong><br />这篇文章中的大部分示例都只是代码片段，但是下面列出的是一个完整的Hello world的示例程序。虽然我们使用的是ATL，但是没有涉及到COM，因此在使用Visual C++&amp;reg;建立项目的时候，我们选择Win32&amp;reg; application而不是ATL COM： <br />在stdafx.h文件中，加入下面几行： <pre>#include &lt;atlbase.h&gt;
extern CComModule _Module;
#include &lt;atlwin.h&gt;
</pre>在hello.cpp文件中，写如下代码：<pre>#include "stdafx.h"
CComModule _Module;
class CMyWindow : public CWindowImpl&lt;CMyWindow&gt; {
   BEGIN_MSG_MAP( CMyWindow )
      MESSAGE_HANDLER( WM_PAINT, OnPaint )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
   END_MSG_MAP()

   LRESULT OnPaint( UINT, WPARAM, LPARAM, BOOL&amp; ){
      PAINTSTRUCT ps;
      HDC hDC = GetDC();
      BeginPaint( &amp;ps );
      TextOut( hDC, 0, 0, _T("Hello world"), 11 );
      EndPaint( &amp;ps );
      return 0;
   }

   LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL&amp; ){
      PostQuitMessage( 0 );
      return 0;
   }
};

int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int )
{
   _Module.Init( NULL, hInstance );

   CMyWindow wnd;
   wnd.Create( NULL, CWindow::rcDefault, _T("Hello"),
      WS_OVERLAPPEDWINDOW|WS_VISIBLE );

   MSG msg;
   while( GetMessage( &amp;msg, NULL, 0, 0 ) ){
      TranslateMessage( &amp;msg );
      DispatchMessage( &amp;msg );
   }

   _Module.Term();
   return msg.wParam;
}
</pre><p>在这个示例程序中，CmyWindow是从CWindowImpl派生的，它的消息映射捕获了两个消息WM_PAINT和WM_DESTROY，当收到WM_PAINT消息时，它的成员函数OnPaint处理这个消息并在窗口上输出“Hello world”，当收到WM_DESTROY消息时，也就是当用户关闭这个窗口的时候，调用OnDestroy函数处理这个消息，在OnDestroy函数中调用PostQuitMessage来结束消息循环。 <br /><br />WinMain函数中创建了一个CmyWindow类的实例并实现了一个标准的消息循环。（有一些地方，我们必须遵循ATL的规范，比如在这里我们必须使用_Module。） <br /><br /><strong><a id="A5" name="A5"></a>消息映射： </strong><br />有三组用于消息映射的宏，他们分别是： </p><ul><li>窗口消息映射宏，用于所有的窗口消息（如WM_CREATE、WM_PAINT等）； 
</li><li>命令消息映射宏，专用于WM_COMMAND消息（比如由控件或菜单发出的消息）； 
</li><li>通知消息映射宏，专用于WM_NOTUFY消息（通常由通用控件发出此消息，比如工具栏控件或列表视图控件） </li></ul><p>窗口消息映射宏： <br />有两个窗口消息映射宏，他们分别是：</p><ul><li>MESSAGE_HANDLER 
</li><li>MESSAGE_RANGE_HANDLER </li></ul><p>第一个宏将一个特定的消息映射到相应的处理函数；第二个宏将一组消息映射到一个处理函数。消息处理函数都要求具有如下的原形： <br /></p><pre>LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&amp; bHandled);
</pre><p>其中，参数uMsg是消息标识，wParam和lParam是两个附加与消息的参数，（他们的具体意义取决与消息类别。） <br /><br />消息处理函数使用bHandled来标志消息是否已经被完全捕获，如果bHandled被设置成FALSE，程序将继续在消息映射表的后续部分查找这个消息的其它处理函数。这个特性使得我们对一个消息使用多个处理函数成为可能。什么时候需要对一个消息使用多个处理函数呢？可能是在对多个类链接时，也可能是我们只想对一个消息做出响应但是并不真正捕获它。在处理函数被调用之前，bHandled被置为TRUE，所以如果我们不在函数的结尾显式地将它置为FALSE，则消息映射表的后续部分不会被继续查找，也不会有其它的处理函数被调用。 <br /><br />命令消息映射宏： <br />命令消息映射宏只处理命令消息（WM_COMMAND消息），但是它能让我们根据消息类型或者发送命令消息的控件ID来指定消息处理函数。 </p><ul><li>COMMAND_HANDLER映射一个特定控件的一条特定消息到一个处理函数； 
</li><li>COMMAND_ID_HANDLER映射一个特定控件的所有消息到一个处理函数； 
</li><li>COMMAND_CODE_HANDLER映射任意控件的一个特定消息到一个处理函数； 
</li><li>COMMAND_RANGE_HANDLER映射一定范围内的控件的所有消息到一个处理函数； 
</li><li>COMMAND_RANGE_CODE_HANDLER映射一定范围内的控件的一条特定消息到一个处理函数。 </li></ul><p>命令消息处理函数应该具有如下的原形： </p><pre>LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&amp; bHandled);
</pre><p>其中，参数wNotifyCode代表消息代码，wID代表发送消息的控件的ID，hWndCtl代表发送消息的控件的窗口句柄，bHandled的意义如前所述。 <br /><br />通知消息映射宏： <br />通知消息映射宏用来处理通知消息（WM_NOTUFY消息），它根据通知消息的类型和发送通知消息的控件的不同将消息映射到不同的处理函数，这些宏与前面讲的命令消息映射宏是等价的，唯一的不同就是它处理的是通知消息而不是命令消息。 </p><ul><li>NOTIFY_HANDLER 
</li><li>NOTIFY_ID_HANDLER 
</li><li>NOTIFY_CODE_HANDLER 
</li><li>NOTIFY_RANGE_HANDLER 
</li><li>NOTIFY_RANGE_CODE_HANDLER </li></ul><p>通知消息处理函数都需要如下的原形：</p><pre>LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL&amp; bHandled);
</pre>其中，参数idCtrl代表发送通知消息的控件的ID，参数pnmh是指向一个NMHDR结构的指针，bHandled的意义如前所述。 <br /><br />通知消息包含了一个指向消息细节的结构的指针，例如，当一个列表视图控件发送一个通知消息，这个消息就包含了一个指向NMLVDISPINFO结构的指针，所有类似于NMLVDISPINFO的结构都包含一个NMHDR结构的头，pnmh就指向这个头，如果需要访问这种结构中头部以外的其它数据成员，可以将pnmh转化成相应类型的指针。 <br /><br />例如，我们如果要处理列表视图控件发出的LVN_ENDLABELEDIT通知消息，我们可以把下面这行代码放到消息映射表中： <pre>NOTIFY_HANDLER( ID_LISTVIEW, LVN_ENDLABELEDIT, OnEndLabelEdit)</pre>这个通知消息附带的额外信息包含在一个NMLVDISPINFO结构中，因此，消息处理函数看起来应该象下面这个样子： <pre>LRESULT OnEndLabelEdit(int idCtrl, LPNMHDR pnmh, BOOL&amp; bHandled)
{
   // The item is -1 if editing is being canceled. 
   if ( ((NMLVDISPINFO*)pnmh)-&gt;item.iItem == -1) return FALSE; 
   ...
</pre>可以看出，pnmh指针被转化成NMLVDISPINFO*类型，以便访问头部结构以外的数据。 <br /><br /><strong><a id="A6" name="A6"></a>为现有的窗口类添加功能：</strong><br />有许多向现有的窗口添加功能的方法。如果这个类是ATL窗口类，我们可以从这个窗口类派生自己的类，就象Base Class Chaining中描述的一样。这种方法主要是一个C++类的继承加上一点消息映射的链接。 <br /><br />如果我们想扩展一个预定义的窗口类（如按纽类或列表框类）的功能，我们可以超类化它。就是创建一个基于这个预定义类的新类，并在消息映射表中添加消息映射以增强它的功能。 <br /><br />有些时候，我们需要改变一个已经存在的窗口实例的行为，而不是一个窗口类——或许我们要让一个对话框上的编辑框做点什么特别的事情。在这种情况下，我们可以写一个新的ATL窗口类，并子类化这个已经存在的编辑框。任何本该发送到这个编辑框的消息都会先被发送到这个子类的对象。 <br /><br />另外一种可选的方法：我们也可以让这个编辑框成为一个被包含的窗口，所有发送到这个编辑框的消息都会经过它的容器窗口；我们可以在这个容器窗口中为这个被包含的窗口实现特殊的消息处理。 <br /><br />最后的一种方法就是消息反射，当一个窗口收到一个消息后不处理它，而是反射给发送这个消息的窗口自己处理，这种技术可以用来创建自包含的控件。 <br /><br /><strong><a id="A7" name="A7"></a>基类消息链（Base Class Chaining）：</strong><br />如果我们已经有一些实现了特定功能的ATL窗口类，我们可以从它们派生新类以充分利用继承的优点。比如： <pre>class CBase: public CWindowImpl&lt; CBase &gt;
// simple base window class: shuts down app when closed
{
   BEGIN_MSG_MAP( CBase )
      MESSAGE_HANDLER( WM_DESTROY, OnDestroy )
   END_MSG_MAP()

   LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      PostQuitMessage( 0 );
      return 0;
   }
};

class CDerived: public CBase
// derived from CBase; handles mouse button events
{
   BEGIN_MSG_MAP( CDerived )
      MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )
   END_MSG_MAP()

   LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      ATLTRACE( "button down\n" );
      return 0;
   }
};

// in WinMain():
   ...
   CDerived win;
   win.Create( NULL, CWindow::rcDefault, "derived window" );
</pre>可是，上面的代码有一个问题。当我们在调试模式下运行这个程序，一个窗口出现了，如果我们在这个窗口中单击，“button down”将出现在输出窗口中，这是CDrived类的功能，可是，当我们关闭这个窗口的时候，程序并不退出，尽管CBase类处理了WM_DESTROY消息并且CDrived类是从CBase类派生的。 <br /><br />Why？因为我们必须明确地将一个消息映射表链接到另外一个。如下： <pre>BEGIN_MSG_MAP( CDerived )
   MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown )
   CHAIN_MSG_MAP( CBase ) // 链接到基类
END_MSG_MAP()
</pre>现在，任何在CDrived类中没有被处理的消息都会被传到CBase类中。 <br /><br />为什么不自动将派生类的消息映射和它的基类的消息映射链接起来呢？这是因为在ATL的体系结构中有很多多重继承的情况，这种情况下没有办法知道究竟应该链接到哪个基类，所以只好让程序员自己来做决定。 <br /><br />可选的消息映射： <br />消息映射链允许多个类同时进行消息处理，同时也带来了问题：如果我们在多个类中都要响应WM_CREATE消息，但是不同的类需要基类提供不同的处理，怎么办呢？为了解决这个问题，ATL使用了可选的消息映射：将消息映射表分成很多节，每一节用不同的数字标识，每一节都是一个可选的消息映射表。 <pre>// in class CBase:
   BEGIN_MSG_MAP( CBase )
      MESSAGE_HANDLER( WM_CREATE, OnCreate1 )
      MESSAGE_HANDLER( WM_PAINT, OnPaint1 )
      ALT_MSG_MAP( 100 )
      MESSAGE_HANDLER( WM_CREATE, OnCreate2 )
      MESSAGE_HANDLER( WM_PAINT, OnPaint2 )
      ALT_MSG_MAP( 101)
      MESSAGE_HANDLER( WM_CREATE, OnCreate3 )
      MESSAGE_HANDLER( WM_PAINT, OnPaint3 )
   END_MSG_MAP()
</pre>如上，基类的消息映射表由3节组成：一个默认的消息映射表（隐含的标识为0）和两个可选的消息映射表（标识为100和101）。 <br /><br />当你链接消息映射表时，指定你所希望的方案的标识，如下： <pre>class CDerived: public CBase {
   BEGIN_MSG_MAP( CDerived )
      CHAIN_MSG_MAP_ALT( CBase, 100 )
   END_MSG_MAP()
   ...
</pre>CDrived类的消息映射表链接到CBase类中标识号为100的可选节，因此当WM_PAINT到达时，CBase::OnPaint2被调用。 <br />（译者注：我觉得这种方法不太合乎C++的思想，基类的编写者不一定总能知道派生自它的类会有哪些需求，而且把所有不同的版本都在基类中实现，基类中无用的代码量会大大增加。更好的办法应该是把基类中的消息处理函数声明为虚函数。总之，我觉得这一小节并不能体现出可选消息映射的真正用途。） <br /><br />其它类型的链： <br />除了基类消息映射链，ATL也提供了成员链（member chaining）和动态链(dynamic chaining)，这些很少使用到的链技术超出了我们这篇文章的讨论范围，但是可以简单提一下。成员链允许把消息映射链接到一个类的成员变量，动态链允许在运行时进行动态链接。如果你想了解更多，请参考ATL文档中的CHAIN_MSG_MAP_DYNAMIC 和CHAIN_MSG_MAP_MEMBER的相关内容。 <br /><br /><strong><a id="A8" name="A8"></a>窗口的超类化：</strong><br />超类化定义一个类，并为预定义的窗口类（如按钮类或列表框类）添加新的功能，下面的例子超类化一个按钮，让这个按钮在被单击的时候发出蜂鸣。 <pre>class CBeepButton: public CWindowImpl&lt; CBeepButton &gt;
{
public:
   DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("Button") )
   BEGIN_MSG_MAP( CBeepButton )
      MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
   END_MSG_MAP()

   LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL&amp; bHandled )
   {
      MessageBeep( MB_ICONASTERISK );
      bHandled = FALSE; // alternatively: DefWindowProc()
      return 0;
   }
}; // CBeepButton
</pre>DECLARE_WND_SUPERCLASS宏声明了这个窗口的类名（“BeepButton”）和被超类化的类名（“Button”）。它的消息映射表只有一个入口项，将WM_LBUTTONDOWN消息映射到OnLButtonDown函数。其余的消息都让默认的窗口过程处理，除了可以发出蜂鸣外，CbeepButton需要和其它的按钮表现相同，因此在OnLButtonDown函数的最后，需要将bHandled设置为FALSE，让默认的窗口过程在OnLButtonDown函数完成后对WM_LBUTTONDOWN消息进行其它的处理。（另外的一种方法是直接调用DefWindowProc函数。） <br /><br />到目前为止，我们所做的只是定义了一个新类；我们依然需要创建一些真正的CbeepButton窗口，下面的类定义了两个CbeepButton类型的成员变量，因此，当这个类的窗口被创建时，将会创建两个CbeepButton类型的子窗口。 <pre>const int ID_BUTTON1 = 101;
const int ID_BUTTON2 = 102;

class CMyWindow: public CWindowImpl&lt; CMyWindow, CWindow,
CWinTraits&lt;WS_OVERLAPPEDWINDOW|WS_VISIBLE&gt; &gt;
{
   CBeepButton b1, b2;

   BEGIN_MSG_MAP( CMyWindow )
      MESSAGE_HANDLER( WM_CREATE, OnCreate )
      COMMAND_CODE_HANDLER( BN_CLICKED, onClick )
   END_MSG_MAP()

   LRESULT onClick(WORD wNotifyCode, WORD wID, HWND hWndCtl,
      BOOL&amp; bHandled)
   {
      ATLTRACE( "Control %d clicked\n", wID );
      return 0;
   }

   LRESULT OnCreate( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      RECT r1 = { 10, 10, 250, 80 };
      b1.Create(*this, r1, "beep1", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON1);
      RECT r2 = { 10, 110, 250, 180 };
      b2.Create(*this, r2, "beep2", WS_CHILD|WS_VISIBLE, 0, ID_BUTTON2);
      return 0;
   }
}; // CMyWindow
</pre><strong><a id="A9" name="A9"></a>窗口的子类化：</strong><br /><br />子类化允许我们改变一个已经存在的窗口的行为，我们经常用它来改变控件的行为。它的实现机制是插入一个消息映射表来截取发向控件的消息。举例说明：假设有一个对话框，对话框上有一个编辑框控件，我们想让这个控件只接受不是数字的字符。我们可以截获发往这个控件的WM_CHAR消息并抛弃接收到的数字字符。下面的类实现这个功能： <pre>class CNoNumEdit: public CWindowImpl&lt; CNoNumEdit &gt;
{
   BEGIN_MSG_MAP( CNoNumEdit )
      MESSAGE_HANDLER( WM_CHAR, OnChar )
   END_MSG_MAP()

   LRESULT OnChar( UINT, WPARAM wParam, LPARAM, BOOL&amp; bHandled )
   {
      TCHAR ch = wParam;
      if( _T(''0'') &lt;= ch &amp;&amp; ch &lt;= _T(''9'') )
         MessageBeep( 0 );
      else
         bHandled = FALSE;
      return 0;
   }
};
</pre>这个类只处理一个消息WM_CHAR，如果这个字符是数字的话，则调用MessageBeep( 0 )并返回，这样可以有效地忽略这个字符。如果不是数字，则将bHandled设置为FALSE，指明默认的窗口过程这个消息需要进一步处理。 <br /><br />现在我们将子类化一个编辑框控件，以便CnoNumEdit能够抢先处理发到这个编辑框得消息。（下面得例子用到了CdialogImpl类，这个类我们将在ATL中的对话框类一节中介绍。）在这个例子中，CmyDialog类中用到了一个对话框资源（ID号为IDD_DIALOG1），对话框中有一个编辑框控件（ID号为IDC_EDIT1），当对话框初始化的时候，编辑框经过SubclassWindow而变成一个不接受数字的编辑框： <pre>class CMyDialog: public CDialogImpl&lt;CMyDialog&gt;
{
public:
   enum { IDD = IDD_DIALOG1 };
   BEGIN_MSG_MAP( CMyDialog )
      MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog )
   END_MSG_MAP()

   LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL&amp; )
   {
      ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) );
      return 0;
   }

   CNoNumEdit ed;
};
</pre><strong><a id="A10" name="A10"></a></strong></div>
		<div>
		</div>
		<br />
		<br />
		<div class="dashed">　</div>
		<br />文章引用自: <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/atlwindow.asp" target="_blank">http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvc60/html/atlwindow.asp</a><img src ="http://www.cppblog.com/yishanhante/aggbug/19687.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-13 09:52 <a href="http://www.cppblog.com/yishanhante/articles/19687.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用ATL建立轻量级的COM对象【二】</title><link>http://www.cppblog.com/yishanhante/articles/19659.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 13:29:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19659.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19659.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19659.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19659.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19659.html</trackback:ping><description><![CDATA[
		<p>
				<b>起步篇</b>
				<br />
				<br />    在本文的<a href="http://www.vckbase.com/document/viewdoc.asp?id=325">第一部分</a>，我们简要介绍了ATL的一些背景知识以及ATL所面向的开发技术和环境。在这一部分 将开始走进ATL，讲述ATL编程的基本方法、原则和必须要注意的问题。<br />    理解ATL最容易的方法是考察它对客户端编程的支持。对于COM编程新手而言，一个棘手的主要问题之一是正确管理接口指针的引用计数。COM的引用计数法则是没有运行时强制 性的，也就是说每一个客户端必须保证对对象的承诺。<br />    有经验的COM编程者常常习惯于使用文档中（如《Inside OLE》）提出的标准模式。调用某个函数或方法，返回接口指针，在某个时间范围内使用这个接口指针，然后释放它。下面是使用这种模式的代码例子： </p>
		<pre>void f(void) {
   IUnknown *pUnk = 0;
   // 调用 
   HRESULT hr = GetSomeObject(&amp;pUnk);
   if (SUCCEEDED(hr)) {
   // 使用
     UseSomeObject(pUnk);
 // 释放
     pUnk-&gt;Release();
   }
}
    </pre>    这个模式在COM程序员心中是如此根深蒂固，以至于他们常常不写实际使用指针的语句，而是先在代码块末尾敲入Release语句。这很像C程序员使用switch语句时的条件反射一样，先敲入break再说。<br />    其实调用Release实在不是什么可怕的负担，但是，客户端程序员面临两个相当严重的问题。第一个问题与获得多接口指针有关。如果某个函数需要在做任何实际工作之前获得三个接口指针，也就是说在第一个使用指针的语句之前必须要由三个调用语句。在书写代码时，这常常意味着程序员需要写许多嵌套条件语句，如：<br /><pre>void f(void) {
  IUnknown *rgpUnk[3];
  HRESULT hr = GetObject(rgpUnk);
  if (SUCCEEDED(hr)) {
    hr = GetObject(rgpUnk + 1);
    if (SUCCEEDED(hr)) {
      hr = GetObject(rgpUnk + 2);
      if (SUCCEEDED(hr)) {
        UseObjects(rgpUnk[0], rgpUnk[1],
                     rgpUnk[2]);
        rgpUnk[2]-&gt;Release();
      }
      rgpUnk[1]-&gt;Release();
    }
    rgpUnk[0]-&gt;Release();
  }
}
    </pre>    像这样的语句常常促使程序员将TAB键设置成一个或两个空格，甚至情愿使用大一点的显示器。但事情并不总是按你想象的那样，由于种种原因项目团队中的COM组件编程人员往往得不到 所想的硬件支持，而且在公司确定关于TAB键的使用标准之前，程序员常常求助于使用有很大争议但仍然有效的“GOTO”语句：<br /><pre>void f(void) {
   IUnknown *rgpUnk[3];
   ZeroMemory(rgpUnk, sizeof(rgpUnk));
   if (FAILED(GetObject(rgpUnk))) 
     goto cleanup;
   if (FAILED(GetObject(rgpUnk+1))) 
     goto cleanup;
   if (FAILED(GetObject(rgpUnk+2))) 
     goto cleanup;
 
   UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
 
 cleanup:
   if (rgpUnk[0]) rgpUnk[0]-&gt;Release();
   if (rgpUnk[1]) rgpUnk[1]-&gt;Release();
   if (rgpUnk[2]) rgpUnk[2]-&gt;Release();
}    </pre>这样的代码虽然不那么专业，但至少减少了屏幕的水平滚动。<br />使用以上这些代码段潜在着更加棘手的问题，那就是在碰到C++异常时。如果函数UseObjects丢出异常，则释放指针的代码被完全屏蔽掉了。 解决这个问题的一个方法是使用Win32的结构化异常处理（SEH）进行终结操作：<br /><pre>void f(void) {
   IUnknown *rgpUnk[3];
   ZeroMemory(rgpUnk, sizeof(rgpUnk));
   __try {
    if (FAILED(GetObject(rgpUnk))) leave;
    if (FAILED(GetObject(rgpUnk+1))) leave;
    if (FAILED(GetObject(rgpUnk+2))) leave;
 
    UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
   } __finally {
    if (rgpUnk[0]) rgpUnk[0]-&gt;Release();
    if (rgpUnk[1]) rgpUnk[1]-&gt;Release();
    if (rgpUnk[2]) rgpUnk[2]-&gt;Release();
}
</pre>    可惜Win32 SHE在C++中的表现并不如想象得那么好。较好的方法是使用内建的C++异常处理模型，同时停止使用没有加工过的指针。标准C++库有一个类：auto_ptr，在其析构函数中定 死了一个操作指针的delete调用（即使在出现异常时也能保证调用）。与之类似，ATL有一个COM智能指针，CComPtr，它的析构函数会正确调用Release。<br />    <a href="http://www.vckbase.com/document/journal/vckbase13/comtech/atl/ccomptr.htm">CComPtr</a>类实现客户端基本的COM引用计数模型。CComPtr有一个数据成员，它是一个未经过任何加工的COM接口指针。其类型被作为模板参数传递：<br /><pre>      CComPtr&lt;IUnknown&gt; unk;
      CComPtr&lt;IClassFactory&gt; cf;
</pre>    缺省的构造函数将这个原始指针数据成员初始化为NULL。智能指针也有构造函数，它的参数要么是原始指针，要么是相同类型的智能参数。不论哪种情况，智能指针都调用AddRef控制引用。CComPtr的赋值操作符 既可以处理原始指针，也可以处理智能指针，并且在调用新分配指针的AddRef之前自动释放保存的指针。最重要的是，CComPtr的析构函数释放保存的接口（如果非空）。请看下列代码：<br /><pre>void f(IUnknown *pUnk1, IUnknown *pUnk2) {
    // 如果非空，构造函数调用pUnk1的AddRef 
    CComPtr<iunknown> unk1(pUnk1);
    // 如果非空，构造函数调用unk1.p的AddRef
    CComPtr<iunknown> unk2 = unk1;
    // 如果非空，operator= 调用unk1.p的Release并且
    //如果非空，调用unk2.p的AddRef
    unk1 = unk2;
    //如果非空，析构函数释放unk1 和 unk2
}
</iunknown></iunknown></pre>    除了正确实现COM的AddRef 和 Release规则之外，CComPtr还允许实现原始和智能指针的透明操作，参见<a href="http://www.vckbase.com/document/journal/vckbase13/comtech/atl/atlcomslider2.htm">附表二</a>所示。也就是说下面的代码按照你所想象的方式运行：<br /><pre>void f(IUnknown *pUnkCO) {
    
    CComPtr<iclassfactory> cf;
    
    HRESULT hr;
    
    // 使用操作符 &amp; 获得对 &amp;cf.p 的存取
    hr = pUnkCO-&gt;QueryInterface(IID_IClassFactory,(void**)&amp;cf);
    if (FAILED(hr)) throw hr;
    
    CComPtr<iunknown> unk;
    
    // 操作符 -&gt; 获得对cf.p的存取
    // 操作符 &amp; 获得对 &amp;unk.p的存取
    hr = cf-&gt;CreateInstance(0, IID_IUnknown, (void**)&amp;unk);
    
    if (FAILED(hr)) throw hr;
    
    // 操作符 IUnknown * 返回 unk.p
    UseObject(unk);
    
    // 析构函数释放unk.p 和 cf.p
}
</iunknown></iclassfactory></pre>    除了缺乏对Release的显式调用外，这段代码像是纯粹的COM代码。有了CComPtr类的武装，前面所遇到的麻烦问题顿时变得简单了：<br /><pre>void f(void) {<br />   CComPtr&lt;IUnknown&gt; rgpUnk[3];<br />   if (FAILED(GetObject(&amp;rgpUnk[0]))) return;<br />   if (FAILED(GetObject(&amp;rgpUnk[1]))) return;<br />   if (FAILED(GetObject(&amp;rgpUnk[2]))) return;<br />   UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);<br />
      }<br /></pre>由于CComPtr对操作符重载用法的扩展，使得代码的编译和运行无懈可击。<br />    假定模板类知道它所操纵的指针类型，你可能会问：那为什么智能指针不能在它的功能操作符或构造函数中自动调用QueryInterface，从而更有效地包装IUnknown呢？在Visual C++ 5.0出来以前，没有办法将某个接口的GUID与它的本身的C++类型关联起来——Visual C++ 5.0用私有的declspec将某个IID与一个接口定义绑定在一起。因为ATL的设计 考虑到了它要与大量不同的C++编译器一起工作，它需要用与编译器无关的手段提供GUID。下面我们来探讨另一个类——CComQIPtr类。<br />    CComQIPtr与CComPtr关系很密切（实际上，它只增加了两个成员函数）。CComQIPtr必须要两个模板参数：一个是被操纵的指针类型 ，另一个是对应于这个指针类型的GUID。例如，下列代码声明了操纵IDataObject 和IPersist接口的智能指针：<br /><pre>      CComQIPtr&lt;IDataObject, &amp;IID_IDataObject&gt; do;
      CComQIPtr&lt;IPersist, &amp;IID_IPersist&gt; p;
</pre>    CComQIPtr的优点是它有重载的构造函数和赋值操作符。同类版本（例如，接受相同类型的接口）仅仅AddRef右边的赋值/初始化操作。这实际上就是CComPtr的功能。异类版本（接受类型不一致的接口）正确调用QueryInterface来决定是否这个对象确实支持所请求的接口：<br /><pre>  
      void f(IPersist *pPersist) {
          CComQIPtr&lt;IPersist, &amp;IID_IPersist&gt; p;
          // 同类赋值 - AddRef''s
          p = pPersist;
    
          CComQIPtr&lt;IDataObject, &amp;IID_IDataObject&gt; do;
          // 异类赋值 - QueryInterface''s
          do = pPersist;
      }
 </pre>    在第二种赋值语句中，因为pPersist是非IDataObject *类型，但它是派生于IUnknown的接口指针，CComQIPtr通过pPersist调用QueryInterface来试图获得这个对象的IDataObject接口指针。如果QueryInterface调用成功，则此智能指针将含有作为结果的原始IDataObject指针。如果QueryInterface调用失败，则do.p将被置为null。如果QueryInterface返回的HRESULT值很重要，但又没有办法从赋值操作获得其值时，则必须显式调用QueryInterface。<br />    既然有了CComQIPtr，那为什么还要CComPtr呢？由几个理由：首先，ATL最初的发布版本只支持CComPtr，所以它就一直合法地保留下来了。其二（也是最重要的理由），由于重载的构造函数和赋值操作，对IUnknown使用CComQIPtr是非法的。因为所有COM接口的类型定义都必须与IUnknown兼容。<br /><pre>      CComPtr&lt;IUnknown&gt; unk;
 </pre>从功能上将它等同于 <pre>      CComQIPtr&lt;IUnknown, &amp;IID_IUnknown&gt; unk;
 </pre>前者正确。后者是错误的用法。如果你这样写了，C++编译器将提醒你改正。<br />    将CComPtr作为首选的另外一个理由可能是一些开发人员相信静悄悄地调用QueryInterface，没有警告，削弱了C++系统的类型。毕竟，C++在没有进行强制类型转换的情况下不允许对类型不一致的原始指针 进行赋值操作，所以为什么要用智能指针的道理也在这，幸运的是开发人员可以选择最能满足需要的指针类型。<br />    许多开发人员将智能指针看成是对过于的复杂编程任务的简化。我最初也是这么认为的。但只要留意它们使用COM智能指针的方法。就会逐渐认识到它们引入的潜在危险与它们解决的问题一样多。<br />关于这一点，我用一个现成的使用原始指针的函数为例：<br /><pre> 
void f(void) {
   IFoo *pFoo = 0;
   HRESULT hr = GetSomeObject(&amp;pFoo);
   if (SUCCEEDED(hr)) {
      UseSomeObject(pFoo);
      pFoo-&gt;Release();
   }
} </pre>将它自然而然转换到使用CComPtr。<br /><pre>  void f(void) {
   CComPtr&lt;IFoo&gt; pFoo = 0;
   HRESULT hr = GetSomeObject(&amp;pFoo);
   if (SUCCEEDED(hr)) {
      UseSomeObject(pFoo);
      pFoo-&gt;Release(); 
   }
}
 </pre>    注意CComPtr 和 CComQIPtr输出所有受控接口成员，包括AddRef和Release。可惜当客户端通过操作符-&gt;的结果调用Release时，智能指针很健忘 ，会二次调用构造函数中的Release。显然这是错误的，编译器和链接器也欣然接受了这个代码。如果你运气好的话，调试器会很快捕获到这个错误。<br />    使用ATL智能指针的另一个要引起注意的风险是类型强制转换操作符对原始指针提供的访问。如果隐式强制转换操作符的使用存在争议。当 ANSI/ISO C++ 委员会在决定采用某个C++串类时，他们明确禁止隐式类型转换。而是要求必须显式使用c_str函数在需要常量char *（const char *）的地方传递标准C++串。ATL提供了一种隐含式的类型转换操作符顺利地解决了这个问题。通常，这个转换操作符可以根据你的喜好来使用，允许你将智能指针传递到需要用原始指针的函数。<br /><pre> void f(IUnknown *pUnk) { 
    CComPtr<iunknown> unk = pUnk;
    // 隐式调用操作符IUnknown *()
    CoLockObjectExternal(unk, TRUE, TRUE);
}
 </iunknown></pre>这段代码能正确运行，但是下面的代码也不会产生警告信息，编译正常通过： <pre>HRESULT CFoo::Clone(IUnknown **ppUnk) { 
   CComPtr<iunknown> unk;
   CoCreateInstance(CLSID_Foo, 0, CLSCTX_ALL,
   IID_IUnknown, (void **) &amp;unk);
   // 隐式调用操作符IUnknown *()
   *ppUnk = unk;
   return S_OK;
}
</iunknown></pre>    在这种情况下，智能指针（unk）对原始值针**ppUnk的赋值触发了与前面代码段相同的强制类型转换。在第一个例子中，不需要用AddRef。在第二个例子中，必须要用AddRef。<br />    有关使用智能指针的更详细一般信息，请参见Scott Meyer的《More Effective C++》（Addison-Wesley, 1995年出版）。国内目前还没有这本书的中译本或影印本。有关COM智能指针的更多特定信息，请参见Don Box的一篇关于智能指针的专题文章http://www.develop.com/dbox/cxx/SmartPointer.htm。 （待续）<br /><img src ="http://www.cppblog.com/yishanhante/aggbug/19659.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 21:29 <a href="http://www.cppblog.com/yishanhante/articles/19659.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CComPtr 和 CComQIPtr </title><link>http://www.cppblog.com/yishanhante/articles/19619.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 03:39:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19619.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19619.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19619.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19619.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19619.html</trackback:ping><description><![CDATA[
		<p>对于操作原始的接口指针是比较麻烦的，需要我们自己控制引用记数、API 调用、异常处理。于是 ATL 提供了</p>
		<p>2个智能指针的模板包装类，CComPtr&lt;&gt; 和 CComQIPtr&lt;&gt;，这两个类都在 &lt;atlbase.h&gt; 中声明。CComQIPtr&lt;&gt; </p>
		<p>包含了 CComPtr&lt;&gt;的所有功能，因此我们可以完全用 CComQIPtr&lt;&gt; 来使用智能接口指针，唯一要说明的一点就</p>
		<p>是：CComQIPtr&lt;&gt; 由于使用了运算符的重载功能，它会自动帮我们调用QueryInterface()函数，因此 </p>
		<p>CComQIPtr&lt;&gt; 唯一的缺点就是不能定义 IUnknown * 指针。</p>
		<p>　　  // 智能指针 smart pointer，按照匈牙利命名法，一般以 sp 开头来表示变量类型<br />　　  CComPtr &lt; IUnknown &gt; spUnk; // 正确<br />　　  // 假设 IFun 是一个接口类型<br />　　  CComPtr &lt; IFun &gt; spFun; // 正确<br />　　  CComQIPtr &lt; IFun &gt; spFun; // 正确<br />　　  CComQIPtr &lt; IFun, &amp;IID_IFun &gt; spFun; // 正确<br />　　  CComQIPtr &lt; IUnknown &gt; spUnk; // 错误！CComQIPtr不能定义IUnknown指针<br />给智能指针赋值的方法：　　  CComQIPtr &lt; IFun &gt; spFun; // 调用构造函数，还没有赋值，被包装的内部</p>
		<p>接口指针为 NULL<br />　　  <br />　　  CComQIPtr &lt; IFun &gt; spFun( pOtherInterface ); // 调用构造函数，内部接口指针赋值为<br />　　  // 通过 pOtherInterface 这个普通接口指针调用QueryInterface()得到的IFun接口指针<br />　　  <br />　　　　  <br />　　  CComQIPtr &lt; IFun &gt; spFun ( pUnknown ); // 调用构造函数，由IUnknown的QueryInterface()得到</p>
		<p>IFun接口指针<br />　　  <br />　　  CComQIPtr &lt; IFun &gt; spFun = pOtherInterface; // = 运算符重载，含义和上面一样<br />　　  spFun = spOtherInterface; // 同上<br />　　  spFun = pUnknown; // 同上<br />　　  <br />　　  pUnknown-&gt;QueryInterface( IID_IFun, &amp;sp ); // 也可以通过QueryInterface赋值<br />　　  <br />　　  // 智能指针赋值后，可以用条件语句判断是否合法有效<br />　　  if ( spFun ){}  // 如果指针有效<br />　　  if ( NULL != spFun ){} // 如果指针有效<br />　　  <br />　　  if ( !spFun ){}  // 如果指针无效<br />　　  if ( NULL == spFun ){} // 如果指针无效<br />智能指针调用函数的方法：　　  spFun.CoCreateInstance(...); // 等价与 API </p>
		<p>函数::CoCreateInstance(...)<br />　　  spFun.QueryInterface(...); // 等价与 API 函数::QueryInterface()<br />　　  <br />　　  spFun-&gt;Add(...); // 调用内部接口指针的接口函数</p>
		<p>　　  // 调用内部接口指针的QueryInterface()函数，其实效果和 spFun.QueryInterface(...) 一样<br />　　  spFun-&gt;QueryInterface(...); <br />　　  <br />　　  spFun.Release(); // 释放内部的接口指针，同时内部指针赋值为 NULL<br />　　  spFun-&gt;Release(); // 错！！！一定不要这么使用。<br />　　  // 因为这个调用并不把内部指针清空，那么析构的时候会被再次释放（释放了两次）<br /></p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19619.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 11:39 <a href="http://www.cppblog.com/yishanhante/articles/19619.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>COM 组件设计与应用（七）编译、注册、调用</title><link>http://www.cppblog.com/yishanhante/articles/19617.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 03:01:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19617.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19617.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19617.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19617.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19617.html</trackback:ping><description><![CDATA[
		<p>
				<b>一、前言</b>
				<br />
				<br />　　<a href="http://www.vckbase.com/document/viewdoc/?id=1497" target="_blank">上两回中</a>，咱们用 ATL 写了第一个 COM 组件程序，这回中，主要介绍编译、注册和调用方法。示例程序你已经下载了吗？如果还没有下载，vc6.0 的用户点<a href="http://www.vckbase.com/code/downcode.asp?id=2720">这里</a>，vc.net 的用户点<a href="http://www.vckbase.com/code/downcode.asp?id=2721">这里</a>。<b><br /></b><br /><br /><b>二、关于编译<br /><i><br />　　2-1 最小依赖</i></b><br />　　“最小依赖”，表示编译器会把 ATL 中必须使用的一些函数静态连接到目标程序中。这样目标文件尺寸会稍大，但独立性更强，安装方便；反之系统执行的时候需要有 ATL.DLL 文件的支持。如何选择设置为“最小依赖”呢？答案是：删除预定义宏“_ATL_DLL”，操作方法见图一、图二。<br /><br /><img height="446" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut7pic1.jpg" width="677" border="0" /><br />图一、在vc6.0中，设置方法<br /><br /><img height="455" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut7pic2.jpg" width="645" border="0" /><br />图二、在 vc.net 2003中，设置方法<br /><br />　　<i><b>2-2 CRT库</b></i><br />　　如果在 ATL 组件程序中调用了 CRT 的运行时刻库函数，比如开平方 sqrt() ，那么编译的时候可能会报错“error LNK2001: unresolved external symbol _main”。怎么办？删除预定义宏“_ATL_MIN_CRT”！操作方法也见图一、图二。（vc.net 2003 中的这个项目属性叫“在 ATL 中最小使用 CRT”）<br /><br />　　<i><b>2-3 MBCS/UNICODE</b></i><br />　　这个不多说了，在预定义宏中，分别使用 _MBCS 或 _UNICODE。<br /><br />　　<i><b>2-4 IDL 的编译</b></i><br />　　COM 在设计初期，就定了一个目标：要能实现跨语言的调用。既然是跨语言的，那么组件的接口描述就必须在任何语言环境中都要能够认识。怎么办？用 .h 文件描述？------ C语言程序员笑了，真方便！BASIC 程序员哭了:-( 因此，微软使用了一个新的文件格式---IDL文件（接口定义描述语言）。IDL 是一个文本文件，它的语言语法比较简单，很象C。具体 IDL 文件的讲解，见下一回《COM 组件设计与应用（八）之添加新接口》。IDL 经过编译，生成二进制的等价类型库文件 TLB 提供给其它语言来使用。图三示意了 ATL COM 程序编译的过程：<br /><br /><img height="330" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut7pic3.jpg" width="561" border="0" /><br />图三、ATL 组件程序编译过程<br /><br />　　说明1：编译后，类型库以 TLB 文件形式单独存在，同时也保存在目标文件的资源中。因此，我们将来在 #import 引入类型库的时候，既可以指定 TLB 文件，也可以指定目标文件；<br />　　说明2：我们作为 C/C++ 的程序员，还算是比较幸福的。因为 IDL 编译后，特意为我们提供了 C 语言形式的接口文件。<br />　　说明3：IDL 编译后生成代理/存根源程序，有：dlldata.c、xxx_p.c、xxxps.def、xxxps.mak，我们可以用 NMAKE.EXE 再次编译来产生真正的代理/存根DLL目标文件(注1)。<br /><br /><br /><b>三、关于注册<br /><br /></b>　　<b><i>情况1：</i></b>当我们使用 ATL 编写组件程序，注册不用我们来负责。编译成功后，IDE 会帮我们自动注册；<br />　　<i><b>情况2：</b></i>当我们使用 MFC 编写组件程序，由于编译器不知道你写的是否是 COM 组件，所以它不会帮我们自动注册。这个时候，我们可以执行菜单“Tools\Register Control”来注册。<br />　　<i><b>情况3：</b></i>当我们写一个具有 COM 功能的 EXE 程序时，注册的方法就是运行一次这个程序；<br />　　<i><b>情况4：</b></i>当我们需要使用第三方提供的组件程序时，可以命令行运行“regsvr32.exe 文件名”来注册。顺便说一句，反注册的方法是“regsvr32.exe /u 文件名”；<br />　　<i><b>情况5：</b></i>当我们需要在程序中（比如安装程序）需要执行注册，那么：</p>
		<pre>typedef HRESULT (WINAPI * FREG)();
TCHAR szWorkPath[ MAX_PATH ];

::GetCurrentDirectory( sizeof(szWorkPath), szWorkPath );	// 保存当前进程的工作目录
::SetCurrentDirectory( 组件目录 );	// 切换到组件的目录

HMODULE hDLL = ::LoadLibrary( 组件文件名 );	// 动态装载组件
if(hDLL)
{
	FREG lpfunc = (FREG)::GetProcAddress( hDLL, _T("DllRegisterServer") );	// 取得注册函数指针
	// 如果是反注册，可以取得"DllUnregisterServer"函数指针
	if ( lpfunc )	lpfunc();	// 执行注册。这里为了简单，没有判断返回值
	::FreeLibrary(hDLL);
}

::SetCurrentDirectory(szWorkPath);	// 切换回原先的进程工作目录
</pre>　　上面的示例，在多数情况下可以简化掉切换工作目录的代码部分。但是，如果这个组件在装载的时候，它需要同时加载一些必须依赖的DLL时，有可能由于它自身程序的 BUG 导致无法正确定位。咳......还是让我们自己写的程序，来弥补它的错误吧......谁让咱们是好人呢 ，谁让咱们的水平比他高呢，谁让咱们在 <a href="http://www.vckbase.com/" target="_blank">vckbase</a> 上是个“榜眼”呢......<br /><br /><br /><b>四、</b><b>关于组件调用<br /><br /></b>　　总的来说，调用组件程序大概有如下方法：<br />　 
<table cellspacing="1" width="100%" border="1"><tbody><tr><td width="22%">#include 方法</td><td width="78%">IDL编译后，为方便C/C++程序员的使用，会产生xxx.h和xxx_i.c文件。我们真幸福，直接#include后就可以使用了</td></tr><tr><td width="22%">#import 方法</td><td width="78%">比较通用的方法，vc 会帮我们产生包装类，让我们的调用更方便</td></tr><tr><td width="22%">加载类型库包装类 方法</td><td width="78%">如果组件提供了 IDispatch 接口，用这个方法调用组件是最简单的啦。不过还没讲IDispatch，只能看以后的文章啦</td></tr><tr><td width="22%">加载ActiveX包装类 方法</td><td width="78%">ActiveX 还没介绍呢，以后再说啦</td></tr></tbody></table><p>　　下载示例程序后，请逐项浏览使用方法：</p><table cellspacing="1" width="100%" border="1"><tbody><tr><td align="middle" width="5%"><b>示例</b></td><td width="11%"><p align="center"><b>方法</b></p></td><td width="177%"><p align="center"><b>简要说明</b></p></td></tr><tr><td align="middle" width="5%">1</td><td width="11%">#include</td><td width="177%">完全用最基本的 API 方式调用组件，使大家熟悉调用原理</td></tr><tr><td align="middle" width="5%">2</td><td width="11%">#include</td><td width="177%">大部分使用 API 方式，使用 CComBSTR 简化对字符串的使用</td></tr><tr><td align="middle" width="5%">3</td><td width="11%">#include</td><td width="177%">展示智能指针 CComPtr&lt;&gt; 的使用方法</td></tr><tr><td align="middle" width="5%">4</td><td width="11%">#include</td><td width="177%">展示智能指针 CComPtr&lt;&gt; 和 CComQIPtr&lt;&gt; 混合的使用方法</td></tr><tr><td align="middle" width="5%">5</td><td width="11%">#include</td><td width="177%">展示智能指针 CComQIPtr&lt;&gt; 的使用方法</td></tr><tr><td align="middle" width="5%">6</td><td width="11%">#include</td><td width="177%">展示智能指针的释放方法</td></tr><tr><td align="middle" width="5%">7</td><td width="11%">#import</td><td width="177%">vc 包装的智能指针 IxxxPtr、_bstr_t、_variant_t 的使用方法和异常处理</td></tr><tr><td align="middle" width="5%">8</td><td width="11%">#import</td><td width="177%">import 后的命名空间的使用方法</td></tr></tbody></table><p>　　示例程序中都写有注释，请读者仔细阅读并同时参考 MSDN 的函数说明。这里，我给大家介绍一下“智能指针”：<br />　　对于操作原始的接口指针是比较麻烦的，需要我们自己控制引用记数、API 调用、异常处理。于是 ATL 提供了2个智能指针的模板包装类，CComPtr&lt;&gt; 和 CComQIPtr&lt;&gt;，这两个类都在 &lt;atlbase.h&gt; 中声明。CComQIPtr&lt;&gt; 包含了 CComPtr&lt;&gt;的所有功能，因此我们可以完全用 CComQIPtr&lt;&gt; 来使用智能接口指针，唯一要说明的一点就是：CComQIPtr&lt;&gt; 由于使用了运算符的重载功能，它会自动帮我们调用QueryInterface()函数，因此 CComQIPtr&lt;&gt; 唯一的缺点就是不能定义 IUnknown * 指针。</p><pre>　　  // 智能指针 smart pointer，按照匈牙利命名法，一般以 sp 开头来表示变量类型
　　  CComPtr &lt; IUnknown &gt; spUnk;	// 正确
　　  // 假设 IFun 是一个接口类型
　　  CComPtr &lt; IFun &gt; spFun;	// 正确
　　  CComQIPtr &lt; IFun &gt; spFun;	// 正确
　　  CComQIPtr &lt; IFun, &amp;IID_IFun &gt; spFun;	// 正确
　　  CComQIPtr &lt; IUnknown &gt; spUnk;	// 错误！CComQIPtr不能定义IUnknown指针</pre>给智能指针赋值的方法：<pre>　　  CComQIPtr &lt; IFun &gt; spFun;	// 调用构造函数，还没有赋值，被包装的内部接口指针为 NULL
　　  
　　  CComQIPtr &lt; IFun &gt; spFun( pOtherInterface );	// 调用构造函数，内部接口指针赋值为
　　  // 通过 pOtherInterface 这个普通接口指针调用QueryInterface()得到的IFun接口指针
　　  
　　  CComQIPtr &lt; IFun &gt; spFun( spOtherInterface ); // 调用构造函数，内部接口指针赋值为
　　  // 通过 spOtherInterface 这个只能接口指针调用QueryInterface()得到的IFun接口指针
　　  
　　  CComQIPtr &lt; IFun &gt; spFun ( pUnknown );	// 调用构造函数，由IUnknown的QueryInterface()得到IFun接口指针
　　  
　　  CComQIPtr &lt; IFun &gt; spFun = pOtherInterface;	// = 运算符重载，含义和上面一样
　　  spFun = spOtherInterface;	// 同上
　　  spFun = pUnknown;	// 同上
　　  
　　  pUnknown-&gt;QueryInterface( IID_IFun, &amp;sp );	// 也可以通过QueryInterface赋值
　　  
　　  // 智能指针赋值后，可以用条件语句判断是否合法有效
　　  if ( spFun ){}		// 如果指针有效
　　  if ( NULL != spFun ){}	// 如果指针有效
　　  
　　  if ( !spFun ){}		// 如果指针无效
　　  if ( NULL == spFun ){}	// 如果指针无效</pre>智能指针调用函数的方法：<pre>　　  spFun.CoCreateInstance(...);	// 等价与 API 函数::CoCreateInstance(...)
　　  spFun.QueryInterface(...);	// 等价与 API 函数::QueryInterface()
　　  
　　  spFun-&gt;Add(...);	// 调用内部接口指针的接口函数

　　  // 调用内部接口指针的QueryInterface()函数，其实效果和 spFun.QueryInterface(...) 一样
　　  spFun-&gt;QueryInterface(...);	
　　  
　　  spFun.Release();	// 释放内部的接口指针，同时内部指针赋值为 NULL
　　  spFun-&gt;Release();	// 错！！！一定不要这么使用。
　　  // 因为这个调用并不把内部指针清空，那么析构的时候会被再次释放（释放了两次）</pre>咳......不说了，不说了，大家多看书，多看MSND，多看示例程序吧。 写累了:-(<br /><br /><b>五、小结</b><br /><br />　　敬请关注《COM 组件设计与应用(八)》------如何增加 ATL 组件中的第二个接口 
<p></p><hr />
注1：编译代理/存根，vc6.0 中稍微麻烦，我们在后面介绍“进程外组件”和“远程组件”的时候再介绍。在 vc.net 2003 下则比较简单，因为代理/存根作为单独的一个工程项目会自动加到我们的解决方案中了。<img src ="http://www.cppblog.com/yishanhante/aggbug/19617.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 11:01 <a href="http://www.cppblog.com/yishanhante/articles/19617.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>COM 组件设计与应用（六）用 ATL 写第一个组件</title><link>http://www.cppblog.com/yishanhante/articles/19606.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 02:40:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19606.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19606.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19606.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19606.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19606.html</trackback:ping><description><![CDATA[
		<strong>一、前言<br /><br /></strong>　　1、与 <a href="http://www.vckbase.com/document/viewdoc/?id=1497" target="_blank"><font color="#002c99">《COM 组件设计与应用(五)》</font></a>的内容基本一致。但本回讲解的是在 vc.net 2003 下的使用方法，即使你不再使用vc6.0，也请和上一回的内容，参照比对。<br />　　2、这第一个组件，除了所有 COM 组件必须的 IUnknown 接口外，我们再实现一个自己定义的接口 IFun，它有两个函数： Add()完成两个数值的加法，Cat()完成两个字符串的连接。<br />　　3、下面......好好听讲! 开始了:-)<br />　 
<p><b>二、</b><b>建立 ATL 工程</b><br /><br />　　步骤2.1：建立一个解决方案。<br />　　步骤2.2：在 该解决方案中，新建一个 vc++ 的 ATL 项目。示例程序叫 Simple2，并选择DLL方式，见图一、图二。<br /><br /><img height="361" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic01.jpg" width="531" border="0" /><br />图一、新建 ATL 项目<br /><br /><img height="449" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic02.jpg" width="615" border="0" /><br />图二、选择非属性化的DLL组件类型<br /><br />　　<i><b>属性化</b></i> 属性化编程，是未来的方向，但我们现在先不要选它。<br />　　<i><b>动态链接库(DLL)</b></i> 选择它。<br />　　<i><b>可执行文件(EXE)</b></i> 以后再讲。<br />　　<i><b>服务(EXE)</b></i> 表示建立一个系统服务组件程序，系统启动后就会加载并执行的程序。<br />　　<b><i>允许合并代理/存根(stub)代码</i></b> 选择该项表示把“代理/存根”代码合并到组件程序中，否则需要单独编译，单独注册代理存根程序。代理/存根，这个是什么概念？还记得我们在<a href="http://www.vckbase.com/document/viewdoc/?id=1493"><font color="#002c99">上回书</font></a>中介绍的吗？当调用者调用进程外或远程组件功能的时候，其实是代理/存根负责数据交换的。关于代理/存根的具体变成和操作，以后再说啦......<br />　　<b><i>支持</i></b><i><b> MFC</b></i> 除非有特殊的原因，我们写 ATL 程序，最好不要选择该项。你可能会说，如果没有MFC的支持，那CString怎么办呀？告诉你个秘密吧，一般人我都不告诉他，我后半辈子就靠着这个秘密活着了：<br />　　1、你会STL吗？可以用 STL 中的 string 代替；<br />　　2、自己写个 MyString 类，嘿嘿；<br />　　3、悄悄地、秘密地、不要告诉别人（特别是别告诉微软），把 MFC 中的 CString 源码拿过来用；<br />　　4、使用 CComBSTR 类，至少也能简化我们字符串操作；<br />　　5、直接用 API 操作字符串，反正我们大家学习 C 语言的时候，都是从这里干起的。（等于没说，呵呵）<br />　　<b><i>支持 COM+ 1.0</i></b> 支持事务处理的 COM+ 功能。COM+ 也许在第 99 回介绍吧。<br /><br /><br /><b>三、添加 ATL 对象类</b><br /><br />　　步骤3.1：菜单"项目\添加类..."（或者用鼠标右键在 项目中弹出菜单"添加\添加类..."）并选择 ATL 简单对象。见图三。<br /><br /><img height="418" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic03.jpg" width="531" border="0" /><br />图三、选择建立ATL简单对象<br /><br />　　除了简单对象(只实现了 IUnknown 接口)，还可以选择“ATL控件”(ActiveX，实现了10多个接口)......可以选择的组件对象类型很多，但本质上，就是让向导帮我们默认加上一些接口。在以后的文章中，陆续介绍吧。<br /><br />　　步骤3.2：增加自定义类 CFun(接口 IFun) ,见图四。<br /><br /> <img height="449" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic04.jpg" width="615" border="0" /><br />图四、填写名称<br /><br />　　其实，我们只需要输入简称，其它的项目会自动填写。没什么多说的，只请大家注意一下 ProgID 项，默认的 ProgID 构造方式为“项目名.简称名”。<br /><br />　　步骤3.3：填写接口属性选项，见图 五。<br /><br /><img height="449" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic05.jpg" width="615" border="0" /><br />图五、接口选项<br /><br />　　<b><i>线程模型</i></b> COM 中的线程，我认为是最讨厌，最复杂的部分。COM 线程和公寓的概念，留待后续介绍。现在吗......大家都选"单元"(Apartment)，它代表什么那？简单地说：当在线程中调用组件函数的时候，这些调用会排队进行。因此，这种模式下，我们可以暂时不用考虑同步的问题。(注1)<br />　　<b><i>接口</i></b>。双重(Dual)，这个非常 非常重要，非常非常常用，但我们今天不讲(注2)。<b>切记！切记！我们的这第一个 COM 程序中，一定要选择“自定义”！！！！</b>（如果你选错了，请删除全部内容，重新来过。）<br />　　<i><b>聚合</b></i> 我们写的组件，将来是否允许被别人聚合(注3)使用。“只能创建为聚合”，有点类似 C++ 中的纯虚类，你要是总工程师，只负责设计但不亲自写代码的话，才选择它。<br />　　<i><b>ISupportErrorInfo</b></i> 是否支持丰富信息的错误处理接口。以后就讲。<br />　　<b><i>连接</i></b><i><b>点</b></i> 是否支持连接点接口（事件、回调）。以后就讲。<br />　　<i><b>IObjectWithSite</b></i> 是否支持IE的调用<br /><br /><br /><b>四、添加接口函数<br /></b><br /><img height="471" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic06.jpg" width="422" border="0" /><br />图六、调出增加接口方法的菜单<br /><br /><img height="449" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic07.jpg" width="615" border="0" /><br />图七、增加接口函数 Add<br /><br />　　请按照图示的方法，增加Add()函数，增加Cat()函数 。[in]表示参数方向是输入；[out]表示参数方向是输出；[out,retval]表示参数方向是输出，同时可以作为函数运算结果的返回值。一个函数中，可以有多个[in]、[out]，但[retval]只能有一个，并且要和[out]组合后在最后一个位置。(注4)<br /><br /><img height="599" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut6pic08.jpg" width="490" border="0" /><br />图八、接口函数定义完成后的图示<br /><br />　　我们都知道，要想改变 C++ 中的类函数，需要修改两个地方：一是头文件(.h)中类的函数声明，二是函数体(.cpp)文件的实现处。而我们现在用 ATL 写组件程序，则还要修改一个地方，就是接口定义(IDL)文件。别着急 IDL 下次就要讨论啦。<br /><br /><br /><b>五、实现接口函数</b><br /><br />　　鼠标双点图八中CFun\基项和接口\Add(...)就可以开始输入函数实现了：</p><pre>STDMETHODIMP CFun::Add(long n1, long n2, long *pVal)
{
	*pVal = n1 + n2;

	return S_OK;
}</pre>这个太简单了，不再浪费“口条”。下面我们实现字符串连接的Cat()函数：<pre>STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
	int nLen1 = ::SysStringLen( s1 );	// s1 的字符长度
	int nLen2 = ::SysStringLen( s2 );	// s2 的字符长度

	*pVal = ::SysAllocStringLen( s1, nLen1 + nLen2 );// 构造新的 BSTR 同时把 s1 先保存进去
	if( nLen2 )
	{
		::memcpy( *pVal + nLen1, s2, nLen2 * sizeof(WCHAR) );	// 然后把 s2 再连接进去
//		wcscat( *pVal, s2 );
	}

	return S_OK;
}</pre>学生：上面的函数实现，完全是调用基本的 API 方式完成的。<br />老师：是的，说实话，的确比较烦琐。<br />学生：我们是用memcpy()完成连接第二个字符串功能的，那么为什么不用函数 wcscat()那？<br />老师：多数情况下可以，但你需要知道：由于BSTR包含有字符串长度，因此实际的BSTR字符串内容中是可以存储L''\0''的，而函数 wcscat() 是以L''\0''作为复制结束标志，因此可能会丢失数据。明白了吗？<br />学生：明白，明白。我看过<a href="http://www.vckbase.com/document/viewdoc/?id=1488" target="_blank"><font color="#002c99">《COM 组件设计与应用(三)之数据类型》</font></a>后就明白了。那么老师，有没有简单一些的方法那？<br />老师：有呀，你看......<pre>STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
	CComBSTR sResult( s1 );
	sResult.AppendBSTR( s2 );

	*pVal = sResult.Copy();
//	*pVal = sResult.Detach();

	return S_OK;
}</pre>学生：哈哈，好！使用了 CComBSTR，这个就简单多了。CComBSTR::Copy()和CComBSTR::Detach()有什么区别？<br />老师：CComBSTR::Copy() 会制造一个 BSTR 的副本，另外CComBSTR::CopyTo()也有类似功能。而CComBSTR::Detach()是使对象与内部的 BSTR 指针剥离，这个函数由于没有复制过程，因此速度稍微快一点点。但要注意，一但剥离后，就不能再使用该对象啦。<br />学生：老师，您讲的太牛啦，我对您的敬仰如巍巍泰山，直入云霄......<br />老师：STOP，STOP！留作业啦......<br />　　1、自己先按照今天讲的内容写出这个组件；<br />　　2、不管你懂不懂，一定要去观察 IDL 文件，CPP 文件；<br />　　3、编译后，看都产生了些什么文件？如果是文本的文件，就打开看看；<br />　　4、下载本文的示例程序(vc.net 2003版本)编译运行，看看效果。然后预习一下示例程序中的调用方法；<br />学生：知道啦，快下课吧，我要上厕所，我都憋的不行了......<br />老师：下课！别忘了顶我的帖子呀......<br /><br /><br /><b>六、小结</b><br /><br />　　本回介绍第一个ATL组件程序的建立步骤，而如何使用该组件，敬请关注《COM 组件设计与应用(七)》。 
<hr />
注1：Apartment，系统通过隐藏的窗口消息来排队组件调用，因此我们可以暂时不考虑同步问题。注意，是暂时哈。<br />注2：双接口表示在一个接口中，同时支持自定义接口和 IDispatch 接口。以后，以后，以后就讲。因为双接口非常重要，我们以后会天天讲、夜夜讲、常常讲------简称“三讲”:)<br />注3：组件的重用方法有2个，聚合和包容。<br />注4：这些都是 IDL 文件中的概念，以后用到什么，就介绍什么。<img src ="http://www.cppblog.com/yishanhante/aggbug/19606.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 10:40 <a href="http://www.cppblog.com/yishanhante/articles/19606.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>COM 组件设计与应用（五）用 ATL 写第一个组件</title><link>http://www.cppblog.com/yishanhante/articles/19605.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 02:39:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19605.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19605.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19605.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19605.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19605.html</trackback:ping><description><![CDATA[
		<strong>一、前言<br /><br /></strong>　　1、如果你在使用 vc5.0 及以前的版本，请你升级为 vc6.0 或 vc.net 2003；<br />　　2、如果你在使用 vc6.0 (ATL 3.0)请阅读本回内容；<br />　　3、如果你在使用 vc.net(ATL 7.0)请阅读下回内容；(当然读读本文内容也不错)<br />　　4、这第一个组件，除了所有 COM 组件必须的 IUnknown 接口外，我们再实现一个自己定义的接口 IFun，它有两个函数： Add()完成两个数值的加法，Cat()完成两个字符串的连接。<br />　　5、下面......好好听讲! 开始了:-) 
<p><b><br />二、</b><b>建立 ATL 工程</b><br />　　步骤2.1：建立一个工作区(WorkSpace)。<br />　　步骤2.2：在工作区中，建立一个 ATL 工程(Project)。示例程序叫 Simple1，并选择DLL方式，见图一。<br /><br /><img height="467" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic01.jpg" width="612" border="0" /><br />图一、建立 ATL DLL 工程<br /><br />　　<i><b>Dynamic Link Library(DLL)</b></i> 表示建立一个 DLL 的组件程序。<br />　　<i><b>Executable(EXE)</b></i> 表示建立一个 EXE 的组件程序。<br />　　<i><b>Service(EXE)</b></i> 表示建立一个服务程序，系统启动后就会加载并执行的程序。<br />　　<i><b>Allow merging of proxy/stub code</b></i> 选择该项表示把“代理/存根”代码合并到组件程序中，否则需要单独编译，单独注册代理存根程序。代理/存根，这个是什么概念？还记得我们在<a href="http://www.vckbase.com/document/viewdoc/?id=1493">上回书</a>中介绍的吗？当调用者调用进程外或远程组件功能的时候，其实是代理/存根负责数据交换的。关于代理/存根的具体变成和操作，以后再说啦......<br />　　<i><b>Support MFC</b></i> 除非有特殊的原因，我们写 ATL 程序，最好不要选择该项。你可能会说，如果没有MFC的支持，那CString怎么办呀？告诉你个秘密吧，一般人我都不告诉他，我后半辈子就靠着这个秘密活着了：<br />　　1、你会STL吗？可以用 STL 中的 string 代替；<br />　　2、自己写个 MyString 类，嘿嘿；<br />　　3、悄悄地、秘密地、不要告诉别人（特别是别告诉微软），把 MFC 中的 CString 源码拿过来用；<br />　　4、使用 CComBSTR 类，至少也能简化我们字符串操作；<br />　　5、直接用 API 操作字符串，反正我们大家学习 C 语言的时候，都是从这里干起的。（等于没说，呵呵）<br />　　<i><b>Support MTS</b></i> 支持事务处理，也就是是否支持 COM+ 功能。COM+ 也许在第 99 回介绍吧。<br /><br /><b><br />三、增加 ATL 对象类<br /></b><br />　　步骤3.1：菜单 Insert\New ATL Object...（或者用鼠标右键在 ClassView 卡片中弹出菜单）并选择Object 分类，选中 Simple Object 项目。见图二。<br /><br /><img height="257" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic02.jpg" width="413" border="0" /><br />图二、选择建立简单COM对象<br /><br />　　<i><b>Category Object</b></i> 普通组件。其中可以选择的组件对象类型很多，但本质上，就是让向导帮我们默认加上一些接口。比如我们选 "Simple Object"，则向导给我们的组件加上 IUnknown 接口；我们选 "Internet Explorer Object"，则向导除了加上 IUnknown 接口外，再增加一个给 IE 所使用的 IObjectWithSite 接口。当然了，我们完全可以手工增加任何接口。<br />　　<i><b>Category Controls</b></i> ActiveX 控件。其中可以选择的 ActiveX 类型也很多。我们在后续的专门介绍 ActiveX 编程中再讨论。<br />　　<i><b>Category Miscellaneous</b></i> 辅助杂类组件。<br />　　<i><b>Categroy Data Access</b></i> 数据库类组件(我最讨厌数据库编程了，所以我也不会)。<br /><br />　　步骤3.2：增加自定义类 CFun(接口 IFun) ,见图三。<br /><br /><img height="260" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic03.jpg" width="421" border="0" /><br />图三、输入类中的各项名称<br />　　 其实，我们只需要输入短名(Short Name)，其它的项目会自动填写。没什么多说的，只请大家注意一下 ProgID 项，默认的 ProgID 构造方式为“工程名.短名”。<br /><br />　　 步骤3.3：填写接口属性，见图四。<br /><img height="260" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic04.jpg" width="421" border="0" /><br />图四、接口属性<br /><br />　　<i><b>Threading Model</b></i> 选择组件支持的线程模型。COM 中的线程，我认为是最讨厌，最复杂的部分。COM 线程和公寓的概念，留待后续介绍。现在吗......大家都选 Apartment，它代表什么那？简单地说：当在线程中调用组件函数的时候，这些调用会排队进行。因此，这种模式下，我们可以暂时不用考虑同步的问题。(注1)<br />　　 <i><b>Interface</b></i> 接口基本类型。Dual 表示支持双接口(注2)，这个非常 非常重要，非常非常常用，但我们今天不讲。Custom 表示自定义借口。<b>切记！切记！我们的这第一个 COM 程序中，一定要选择它！！！！</b>（如果你选错了，请删除全部内容，重新来过。）<br />　　 Aggregation 我们写的组件，将来是否允许被别人聚合(注3)使用。Only 表示必须被聚合才能使用，有点类似 C++ 中的纯虚类，你要是总工程师，只负责设计但不亲自写代码的话，才选择它。<br />　　<i><b>Support ISupportErrorInfo</b></i> 是否支持丰富信息的错误处理接口。以后就讲。<br />　　<i><b>Support Connection Points</b></i> 是否支持连接点接口（事件、回调）。以后就讲。<br />　　<i><b>Free Threaded Marshaler</b></i> 以后也不讲，就算打死你，我也不说！(注4)<br /><br /><b><br />四、添加接口函数<br /></b><br /><img height="365" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic05.jpg" width="282" border="0" /><br />图五、调出增加接口方法的菜单<br /><br /><img height="311" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic06.jpg" width="486" border="0" /><br />图六、增加接口函数 Add<br /><br /><img height="311" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic07.jpg" width="486" border="0" /><br />图七、增加接口函数 Cat<br /><br />　　请严格按照图六的方式，增加Add()函数；由于图七中增加Cat()函数的参数比较长，我没有适当的输入空格，请大家自己输入的时候注意一下。[in]表示参数方向是输入；[out]表示参数方向是输出；[out,retval]表示参数方向是输出，同时可以作为函数运算结果的返回值。一个函数中，可以有多个[in]、[out]，但[retval]只能有一个，并且要和[out]组合后在最后一个位置。(注5)<br /><img height="454" src="http://www.vckbase.net/document/journal/vckbase43/images/comtut5pic08.jpg" width="535" border="0" /><br />图八、接口函数定义完成后的图示<br />　　我们都知道，要想改变 C++ 中的类函数，需要修改两个地方：一是头文件(.h)中类的函数声明，二是函数体(.cpp)文件的实现处。而我们现在用 ATL 写组件程序，则还要修改一个地方，就是接口定义(IDL)文件。别着急 IDL 下次就要讨论啦。<br />　　由于 vc6.0 的BUG，导致大家在增加完接口和接口函数后，可能不会向上图（图八）所表现的样式。解决方法：<br />　 
</p><table cellspacing="1" width="100%" border="1"><tbody><tr><td align="middle" width="3%">1</td><td width="60%">关闭工程，然后重新打开</td><td width="37%">该方法常常有效</td></tr><tr><td align="middle" width="3%">2</td><td width="60%">关闭 IDE，然后重新运行</td><td width="37%">　</td></tr><tr><td align="middle" width="3%">3</td><td width="60%">打开 IDL 文件，检查接口函数是否正确，如不正确请修改</td><td width="37%">　</td></tr><tr><td align="middle" width="3%">4</td><td width="60%">打开 IDL 文件，随便修改一下(加一个空格，再删除这个空格)，然后保存</td><td width="37%">该方法常常有效</td></tr><tr><td align="middle" width="3%">5</td><td width="60%">打开 h/cpp 文件，检查函数是否存在或是否正确，有则改之</td><td width="37%">无则嘉勉，不说完这个成语心理别扭</td></tr><tr><td align="middle" width="3%">6</td><td width="60%">删除 IDL/H/CPP 中的接口函数，然后</td><td width="37%">再来一遍</td></tr><tr><td align="middle" width="3%">7</td><td width="60%">重新建立工程、重新安装vc、重新安装windows、砸计算机</td><td width="37%">砸！</td></tr></tbody></table><p><b><br />五、实现接口函数</b><br /><br />　　鼠标双点图八中CFun\IFun\Add(...)就可以开始输入函数实现了：</p><pre>STDMETHODIMP CFun::Add(long n1, long n2, long *pVal)
{
	*pVal = n1 + n2;

	return S_OK;
}</pre>这个太简单了，不再浪费“口条”。下面我们实现字符串连接的Cat()函数：<pre>STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
	int nLen1 = ::SysStringLen( s1 );	// s1 的字符长度
	int nLen2 = ::SysStringLen( s2 );	// s2 的字符长度

	*pVal = ::SysAllocStringLen( s1, nLen1 + nLen2 );	// 构造新的 BSTR 同时把 s1 先保存进去
	if( nLen2 )
	{
		::memcpy( *pVal + nLen1, s2, nLen2 * sizeof(WCHAR) );	// 然后把 s2 再连接进去
//		wcscat( *pVal, s2 );
	}

	return S_OK;
}</pre>学生：上面的函数实现，完全是调用基本的 API 方式完成的。<br />老师：是的，说实话，的确比较烦琐。<br />学生：我们是用memcpy()完成连接第二个字符串功能的，那么为什么不用函数 wcscat()那？<br />老师：多数情况下可以，但你需要知道：由于BSTR包含有字符串长度，因此实际的BSTR字符串内容中是可以存储L''\0''的，而函数 wcscat() 是以L''\0''作为复制结束标志，因此可能会丢失数据。明白了吗？<br />学生：明白，明白。我看过《<a href="http://www.vckbase.com/document/viewdoc/?id=1488">COM 组件设计与应用(三)</a>之数据类型》后就明白了。那么老师，有没有简单一些的方法那？<br />老师：有呀，你看......<pre>STDMETHODIMP CFun::Cat(BSTR s1, BSTR s2, BSTR *pVal)
{
	CComBSTR sResult( s1 );
	sResult.AppendBSTR( s2 );

	*pVal = sResult.Copy();
//	*pVal = sResult.Detach();

	return S_OK;
}</pre>学生：哈哈，好！使用了 CComBSTR，这个就简单多了。CComBSTR::Copy()和CComBSTR::Detach()有什么区别？<br />老师：CComBSTR::Copy() 会制造一个 BSTR 的副本，另外CComBSTR::CopyTo()也有类似功能。而CComBSTR::Detach()是使对象与内部的 BSTR 指针剥离，这个函数由于没有复制过程，因此速度稍微快一点点。但要注意，一但剥离后，就不能再使用该对象啦。<br />学生：老师，您讲的太牛啦，我对您的敬仰如巍巍泰山，直入云霄......<br />老师：STOP，STOP！留作业啦......<br />　　 1、自己先按照今天讲的内容写出这个组件；<br />　　 2、不管你懂不懂，一定要去观察 IDL 文件，CPP 文件；<br />　　 3、编译后，看都产生了些什么文件？如果是文本的文件，就打开看看；<br />　　 4、下载本文的示例程序(vc6.0版本)编译运行，看看效果。然后预习一下示例程序中的调用方法；<br />学生：知道啦，快下课吧，我要上厕所，我都憋的不行了......<br />老师：下课！别忘了顶我的帖子呀......<br /><br /><br /><b>六、小结</b><br /><br />　　本回介绍第一个ATL组件程序的建立步骤，而如何使用该组件，敬请关注《COM 组件设计与应用(七)》。<br />　 
<hr />
注1：Apartment，系统通过隐藏的窗口消息来排队组件调用，因此我们可以暂时不考虑同步问题。注意，是暂时哈。<br />注2：双接口表示在一个接口中，同时支持自定义接口和 IDispatch 接口。以后，以后，以后就讲。因为双接口非常重要，我们以后会天天讲、夜夜讲、常常讲------简称“三讲”:)<br />注3：组件的重用方法有2个，聚合和包容。<br />注4：名称的功能很好听，但微软根本就没有实现。<br />注5：这些都是 IDL 文件中的概念，以后用到什么，就介绍什么。<br /><img src ="http://www.cppblog.com/yishanhante/aggbug/19605.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 10:39 <a href="http://www.cppblog.com/yishanhante/articles/19605.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用ATL建立轻量级的COM对象【一】</title><link>http://www.cppblog.com/yishanhante/articles/19602.html</link><dc:creator>jay</dc:creator><author>jay</author><pubDate>Mon, 12 Mar 2007 02:01:00 GMT</pubDate><guid>http://www.cppblog.com/yishanhante/articles/19602.html</guid><wfw:comment>http://www.cppblog.com/yishanhante/comments/19602.html</wfw:comment><comments>http://www.cppblog.com/yishanhante/articles/19602.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yishanhante/comments/commentRss/19602.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yishanhante/services/trackbacks/19602.html</trackback:ping><description><![CDATA[
		<p>本文假设你熟悉C++和COM。<br /><br /><b>摘要：</b><br />    ATL——活动模板库（The Active Template Library），其设计旨在让人们用C++方便灵活地开发COM对象。ATL本身相当小巧灵活，这是它最大的优点。用它可以创建轻量级的，自包含的，可复用的二进制代码，不用任何附加的运行时DLLs支持。<br />    由于COM技术良好的口碑，越来越多的程序员已经走进或正在走进COM的编程世界。它就像盛夏里的冰镇啤酒，从来不会让你失望。可惜作为一个C++程序员来说，C++从不与我分享COM的极致以及我对COM的情有独钟。 <br />    C++与COM之间若即若离，和平共处，一次又一次在每个对象中用同样简洁的几行代码实现IUnknown。我敢肯定将来C++编译器和链接器会实现C++对象和COM对象之间自然 的无意识的对应和映射，目前这个环境只存在于实验室中，因此它肯定不是一个你我今天可以购买的产品。眼下可得到的最接近这个环境的东西就是活动模板库——ATL。<br /><br /><b>为什么使用ATL?<br />    </b>ATL是在单层（single-tier）应用逐渐过时，分布式应用逐渐成为主流这样一个环境中诞生的， 它最初的版本是在四个C++头文件中，其中有一个还是空的。它所形成的出色的构架专门用于开发现代分布式应用所需的轻量级COM组件。作为一个模块化的标准组件，ATL不像MFC有厚重的基础结构，省时好用的库使得成百上千的程序员一次又一次轻松实现IUnknown 和IClassFactory。<br />    ATL的构架并不打算包罗万象，无所不能。其第一个版本对实现IUnknown，IClassFactory，IDispatch，IconnectionPointContainer及COM枚举提供非常 到位的支持。第二个版本除了可以编写ActiveX控件外，还对最初的第一个版本中ATL类进行了增强。ATL不提供集合（collections）和串（strings）的处理 ，它假设你用标准的C++库进行这些处理；不支持ODBC——这个世界正在转移到基于COM的不需要包装的数据存取方式；不支持WinSock打包类--sockets本身也是新的东西；ATL也不支持完整的Win32 API打包类——ATL2.0的实现机制提供了对话框和WndProcs支持。此外ATL中没有MFC中的文档/视图模型。取而代之的是ATL那更具伸缩性和灵活 性的通过COM接口（如ActiveX控件）与基于UI的对象之间的沟通模式。<br />    使用正确的工具非常关键。如果你正在编写一个不可见的COM组件，那么ATL与MFC比起来，从开发效率，可伸缩性，运行时性能以及可执行文件大小各方面来看，ATL可能 都是最好的选择。对于现代基于ActiveX控件的用户界面，ATL所产生的代码也比MFC更小更快。另一方面，与MFC的类向导相比，ATL需要更多的COM知识。ATL与STL一样，对于单层应用没什么帮助，而MFC在这方面保持着它的优势。<br />    ATL的设计在很大程度上来自STL的灵感，STL与所有ANSI/ISO兼容的C++编译器一起已经被纳入成为标准C++库的一部分。像STL一样，ATL大胆使用C++模板。模板是C++中众多具有争议的特性之一。每每使用不当都会导致执行混乱，降低性能 和难以理解的代码。明智地使用模板所产生的通用性效果和类型安全特性则是其它方法所望尘莫及的。ATL与STL一样陷入了两个极端。幸运的是 在L大胆使用C++模板的同时，编译器和链接器技术也在以同样的步伐向前发展。为当前和将来的开发进行STL和ATL的合理选择。<br />    尽管模板在内部得到广泛的使用，但是在用ATL技术时，你不用去敲入或关心那些模板中的尖括弧。因为ATL本身带有ATL对象向导（参见图一）：<br /><br /><img height="233" src="http://www.vckbase.net/document/journal/vckbase13/images/atlcomjoy1.gif" width="400" border="0" /><br /><br /><img height="265" src="http://www.vckbase.net/document/journal/vckbase13/images/atlcomjoy2.gif" width="400" border="0" /><br /><br /><img height="265" src="http://www.vckbase.net/document/journal/vckbase13/images/atlcomjoy3.gif" width="400" border="0" /><br />图一 ATL 对象向导<br /><br /><br />    对象向导产生大量基于ATL模板类缺省的对象实现代码（即框架代码）。这些缺省的对象类型如<a href="http://www.vckbase.net/document/journal/vckbase13/comtech/atl/atlcomslider1.htm">附表一</a>所列。ATL对象向导允许任何人 快速建立COM对象并且在分分钟之内让它运行起来，不用去考虑COM或ATL的细节问题。当然，为了能充分驾驭ATL，你必须掌握C++，模板和COM编程技术。对于大型的对象类，只要在ATL对象向导所产生的缺省实现（框架代码）中加入方法实现来输出定制接口，这也是大多数开发人员开始实现COM对象时的重点所在。<br />    初次接触ATL时，其体系结构给人的感觉是神秘和不可思议。<xxxxime xime="7"><a href="http://www.vckbase.net/document/journal/vckbase13/comtech/atl/HelloATL.htm" target="_blank">HelloATL</a></xxxxime>是一个最简单的基于ATL的进程内服务器源代码 以及用SDK（纯粹用C++编写）实现的同样一个进程内服务器源代码。在真正构建出一个COM组件之前，代码需要经过反反复复多次斟酌和修改。对于想加速开发COM组件速度的主流组件开发人员来说，ATL体系结构并不是什么大问题，因为对象向导产生了所需要的全部框架代码，只 要你加入方法定义即可。对于认真的COM开发人员和系统编程人员来说，ATL提供了一个用C++建立COM组件的高级的，可扩展的体系结构。一旦你理解和掌握了这个体系结构并能驾驭对象向导，你就会看到ATL的表现能力和强大的功能 ，它完全可以和原始的COM编程技术媲美。<br />    另外一个使用ATL开发COM组件的理由是Visual C++ 5.0+集成开发环境（IDE）对ATL的高度支持。 微软在Visual C++ 5.0+中将ATL所要用到的接口定义语言（IDL）集成到了C++编辑器中。（待续）<br /><br /></p>
<img src ="http://www.cppblog.com/yishanhante/aggbug/19602.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yishanhante/" target="_blank">jay</a> 2007-03-12 10:01 <a href="http://www.cppblog.com/yishanhante/articles/19602.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>