﻿<?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++博客-旅途-文章分类-COM+/DCOM</title><link>http://www.cppblog.com/mydriverc/category/4807.html</link><description>如果想飞得高，就该把地平线忘掉</description><language>zh-cn</language><lastBuildDate>Mon, 19 May 2008 13:37:05 GMT</lastBuildDate><pubDate>Mon, 19 May 2008 13:37:05 GMT</pubDate><ttl>60</ttl><item><title>COM 组件设计与应用（一）----重读一遍,别有一番感觉</title><link>http://www.cppblog.com/mydriverc/articles/29061.html</link><dc:creator>旅途</dc:creator><author>旅途</author><pubDate>Tue, 31 Jul 2007 02:20:00 GMT</pubDate><guid>http://www.cppblog.com/mydriverc/articles/29061.html</guid><wfw:comment>http://www.cppblog.com/mydriverc/comments/29061.html</wfw:comment><comments>http://www.cppblog.com/mydriverc/articles/29061.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mydriverc/comments/commentRss/29061.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mydriverc/services/trackbacks/29061.html</trackback:ping><description><![CDATA[
		<p>
				<b>一、前言</b>
				<br />
				<br />　　公元一九九五年某个夜黑风高的晚上，我的一位老师跟我说：“小杨呀，以后写程序就和搭积木一样啦。你赶快学习一些OLE的技术吧......”，当时我心里就寻思 ：“开什么玩笑？搭积木方式写程序？再过100年吧......”，但作为一名听话的好学生，我开始在书店里“踅摸”（注1）有关OLE的书籍（注2）。功夫不负有心人，终于买到了我的第一本COM书《OLE2 高级编程技术》，这本800多页的大布头花费了我1/5的月工资呀......于是开始日夜耕读.....<br />    功夫不负有心人，我坚持读完了全部著作，感想是：这本书，在说什么呐？<br />    功夫不负有心人，我又读完了一遍大布头，感想是：咳~~~，没懂！<br />    功夫不负有心人，我再，我再,我再读 ... 感想是：哦~~~，读懂了一点点啦，哈哈哈。<br />    ......  ......<br />    功夫不负有心人，我终于，我终于懂了。<br />    800页的书对现在的我来说，其实也就10几页有用。到这时候才体会出什么叫“书越读越薄”的道理了。到后来，能买到的书也多了，上网也更方便更便宜了......<br /><br />　　为了让VCKBASE上的朋友，不再经历我曾经的痛苦、不再重蹈我“无头苍蝇”般探索的艰辛、为了VCKBASE的蓬勃发展、为了中国软件事业的腾飞（糟糕，吹的太也高了）......我打算节约一些在 BBS 上赚分的时间，写个系列论文，就叫“COM组件设计与应用”吧。今天是第一部分——起源。<br /><br /><b>二、文件的存储</b><br /><br />　　传说350年前，牛顿被苹果砸到了头，于是发现了万有引力。但到了二十一世纪的现在，任何一个技术的发明和发展，已经不再依靠圣人灵光的一闪。技术的进步转而是被社会的需求、商业的利益、竞争的压力、行业的渗透等推动的。微软在Windows平台上的组件技术也不例外，它的发明，有其必然因素。什么是这个因素那？答案是——文件的存储。<br />　　打开记事本程序，输入了一篇文章后，保存。——这样的文件叫“非结构化文件”；<br />　　打开电子表格程序，输入一个班的学生姓名和考试成绩，保存。——这样的文件叫“标准结构化文件”；<br />　　在我们写的程序中，需要把特定的数据按照一定的结构和顺序写到文件中保存。——这样的文件叫“自定义结构化文件”；（比如 *.bmp 文件）<br />　　以上三种类型的文件，大家都见的多了。那么文件存储就依靠上述的方式能满足所有的应用需求吗？恩~~~，至少从计算机发明后的50多年来，一直是够用的了。嘿嘿，下面看看商业利益的推动作用，对文件 的存储形式产生了什么变化吧。30岁以上的朋友，我估计以前都使用过以下几个著名的软件：WordStar（独霸DOS下的英文编辑软件），WPS（裘伯君写的中文编辑软件，据说当年的市场占有率高达90%，各种计算机培训班的必修课程），LOTUS-123（莲花公司出品的电子表格软件）......<br />    微软在成功地推出 Windows 3.1 后，开始垂涎桌面办公自动化软件领域。微软的 OFFICE 开发部门，各小组分别独立地开发了 WORD 和 EXCEL 等软件，并采用“自定义结构”方式，对文件进行存储。在激烈的市场竞争下，为了打败竞争对手，微软自然地产生了一个念头------如果我能在 WORD 程序中嵌入 EXCEL，那么用户在购买了我 WORD 软件的情况下，不就没有必要再买 LOTUS-123 了吗？！“恶毒”（中国微软的同志们看到了这个词，不要激动，我是加了引号的呀）的计划产生后，他们开始了实施工作，这就是 COM 的前身 OLE 的起源（注3）。但立刻就遇到了一个严重的技术问题：需要把 WORD 产生的 DOC 文件和 EXCEL 产生的 XLS 文件保存在一起。<br />　 
</p>
		<table cellspacing="1" width="100%" border="1">
				<tbody>
						<tr>
								<td width="33%">
										<p align="center">方案</p>
								</td>
								<td width="31%">
										<p align="center">优点</p>
								</td>
								<td width="103%">
										<p align="center">缺点</p>
								</td>
						</tr>
						<tr>
								<td width="33%">建立一个子目录，把 DOC、XLS 存储在这同一个子目录中。</td>
								<td width="31%">数据隔离性好，WORD 不用了解 EXCEL 的存储结构；容易扩展。</td>
								<td width="103%">结构太松散，容易造成数据的损坏或丢失。<br />不易携带。</td>
						</tr>
						<tr>
								<td width="33%">修改文件存储结构，在DOC结构基础上扩展出包容 XLS 的结构。</td>
								<td width="31%">结构紧密，容易携带和统一管理。</td>
								<td width="103%">WORD 的开发人员需要通晓 EXCEL 的存储格式；缺少扩展性，总不能新加一个类型就扩展一下结构吧？！</td>
						</tr>
				</tbody>
		</table>
		<p>    以上两个方案，都有严重的缺陷，怎么解决那？如果能有一个新方案，能够合并前两个方案的优点，消灭缺点，该多好呀......微软是作磁盘***作系统起家的，于是很自然地他们提出了一个非常完美的设计方案，那就是把磁盘文件的管理方式移植到文件中了------复合文件，俗称“文件中的文件系统”。连微软当年都没有想到，就这么一个简单的想法，居然最后就演变出了 COM 组件程序设计的方法。可以说，复合文件是 COM 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图：<br /><img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.vckbase.com/document/journal/vckbase43/images/stmtutpic1.jpg" onload="javascript:if(this.width&gt;500){this.resized=true;this.style.width=500;}" border="0" /><br />图一、左侧表示一个磁盘下的文件组织方式，右侧表示一个复合文件内部的数据组织方式。<br /><br /><b>三、复合文件的特点</b></p>
		<ol>
				<li>复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意，由于使用的是单向指针，因此当做定位***作的时候，向后定位比向前定位要快； 
</li>
				<li>复合文件中的“流对象”，是真正保存数据的空间。它的存储单位为512字节。也就是说，即使你在流中只保存了一个字节的数据，它也要占据512字节的文件空间。啊~~~，这也太浪费了呀？不浪费！因为文件保存在磁盘上，即使一个字节也还要占用一个“簇”的空间那； 
</li>
				<li>不同的进程，或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰； 
</li>
				<li>大家都有这样的体会，当需要往一个文件中插入一个字节的话，需要对整个文件进行***作，非常烦琐并且效率低下。而复合文件则提供了非常方便的“增量访问”能力； 
</li>
				<li>当频繁地删除文件，复制文件后，磁盘空间会变的很零碎，需要使用磁盘整理工具进行重新整合。和磁盘管理非常相似，复合文件也会产生这个问题，在适当的时候也需要整理，但比较简单，只要调用一个函数就可以完成了。 </li>
		</ol>
		<p>
				<b>四、浏览复合文件</b>
				<br />
				<br />　　VC6.0 附带了一个工具软件“复合文件浏览器”，文件名是“vc目录\Common\Tools\<strong>DFView.exe</strong>”。为了方便使用该程序，可以把它加到工具(tools)菜单中。方法是：Tools\Customize...\Tools卡片中增加新的项目。运行 DFView.exe，就可以打开一个复合文件进行观察了（注4）。但奇怪的是，在 Microsoft Visual Studio .NET 2003 中，我反而找不到这个工具程序了,汗！不过这恰好提供给大家一个练习的机会，在你阅读完本篇文章并掌握了编程方法后，自己写一个“复合文件浏览编辑器”程序，又练手了，还有实用的价值。<br /><br /><b>五</b><b>、复合文件函数</b><br /><br />　　复合文件的函数和磁盘目录文件的***作非常类似。所有这些函数，被分为3种类型：WIN API 全局函数，存储 IStorage 接口函数，流 IStream 接口函数。什么是接口？什么是接口函数？以后的文章中再陆续介绍，这里大家只要把“接口”看成是完成一组相关***作功能的函数集合就可以了。<br />　 
</p>
		<table cellspacing="1" width="100%" border="1">
				<tbody>
						<tr>
								<td width="17%">
										<p align="center">
												<b>WIN API 函数</b>
										</p>
								</td>
								<td width="46%">
										<p align="center">
												<b>功能说明</b>
										</p>
								</td>
						</tr>
						<tr>
								<td width="16%">StgCreateDocfile()</td>
								<td width="47%">建立一个复合文件，得到根存储对象</td>
						</tr>
						<tr>
								<td width="16%">StgOpenStorage()</td>
								<td width="47%">打开一个复合文件，得到根存储对象</td>
						</tr>
						<tr>
								<td width="16%">StgIsStorageFile()</td>
								<td width="46%">判断一个文件是否是复合文件</td>
						</tr>
						<tr>
								<td width="100%" colspan="2">
										<p align="center">　</p>
								</td>
						</tr>
						<tr>
								<td width="16%">
										<p align="center">
												<b>IStorage 函数</b>
										</p>
								</td>
								<td width="46%">
										<p align="center">
												<b>功能说明</b>
										</p>
								</td>
						</tr>
						<tr>
								<td width="16%">CreateStorage()</td>
								<td width="46%">在当前存储中建立新存储，得到子存储对象</td>
						</tr>
						<tr>
								<td width="16%">CreateStream()</td>
								<td width="46%">在当前存储中建立新流，得到流对象</td>
						</tr>
						<tr>
								<td width="16%">OpenStorage()</td>
								<td width="46%">打开子存储，得到子存储对象</td>
						</tr>
						<tr>
								<td width="16%">OpenStream()</td>
								<td width="46%">打开流，得到流对象</td>
						</tr>
						<tr>
								<td width="16%">CopyTo()</td>
								<td width="46%">复制存储下的所有对象到目标存储中，该函数可以实现“整理文件，释放碎片空间”的功能</td>
						</tr>
						<tr>
								<td width="16%">MoveElementTo()</td>
								<td width="46%">移动对象到目标存储中</td>
						</tr>
						<tr>
								<td width="16%">DestoryElement()</td>
								<td width="46%">删除对象</td>
						</tr>
						<tr>
								<td width="16%">RenameElement()</td>
								<td width="46%">重命名对象</td>
						</tr>
						<tr>
								<td width="16%">EnumElements()</td>
								<td width="46%">枚举当前存储中所有的对象</td>
						</tr>
						<tr>
								<td width="16%">SetElementTimes()</td>
								<td width="46%">修改对象的时间</td>
						</tr>
						<tr>
								<td width="16%">SetClass()</td>
								<td width="46%">在当前存储中建立一个特殊的流对象，用来保存CLSID（注5）</td>
						</tr>
						<tr>
								<td width="16%">Stat()</td>
								<td width="46%">取得当前存储中的系统信息</td>
						</tr>
						<tr>
								<td width="16%">Release()</td>
								<td width="46%">关闭存储对象</td>
						</tr>
						<tr>
								<td width="62%" colspan="2">　</td>
						</tr>
						<tr>
								<td width="16%">
										<p align="center">
												<b>IStream 函数</b>
										</p>
								</td>
								<td width="46%">
										<p align="center">
												<b>功能说明</b>
										</p>
								</td>
						</tr>
						<tr>
								<td width="16%">Read()</td>
								<td width="46%">从流中读取数据</td>
						</tr>
						<tr>
								<td width="16%">Write()</td>
								<td width="46%">向流中写入数据</td>
						</tr>
						<tr>
								<td width="16%">Seek()</td>
								<td width="46%">定位读写位置</td>
						</tr>
						<tr>
								<td width="16%">SetSize()</td>
								<td width="46%">设置流尺寸。如果预先知道大小，那么先调用这个函数，可以提高性能</td>
						</tr>
						<tr>
								<td width="16%">CopyTo()</td>
								<td width="46%">复制流数据到另一个流对象中</td>
						</tr>
						<tr>
								<td width="16%">Stat()</td>
								<td width="46%">取得当前流中的系统信息</td>
						</tr>
						<tr>
								<td width="16%">Clone()</td>
								<td width="46%">克隆一个流对象，方便程序中的不同模块***作同一个流对象</td>
						</tr>
						<tr>
								<td width="16%">Release()</td>
								<td width="46%">关闭流对象</td>
						</tr>
						<tr>
								<td width="62%" colspan="2">　</td>
						</tr>
						<tr>
								<td align="middle" width="16%">
										<b>WIN API 补充函数</b>
								</td>
								<td align="middle" width="46%">
										<b>功能说明</b>
								</td>
						</tr>
						<tr>
								<td width="16%">WriteClassStg()</td>
								<td width="46%">写CLSID到存储中，同IStorage::SetClass()</td>
						</tr>
						<tr>
								<td width="16%">ReadClassStg()</td>
								<td width="46%">读出WriteClassStg()写入的CLSID，相当于简化调用IStorage::Stat()</td>
						</tr>
						<tr>
								<td width="16%">WriteClassStm()</td>
								<td width="46%">写CLSID到流的开始位置</td>
						</tr>
						<tr>
								<td width="16%">ReadClassStm()</td>
								<td width="46%">读出WriteClassStm()写入的CLSID</td>
						</tr>
						<tr>
								<td width="16%">WriteFmtUserTypeStg()</td>
								<td width="46%">写入用户指定的剪贴板格式和名称到存储中</td>
						</tr>
						<tr>
								<td width="16%">ReadFmtUserTypeStg()</td>
								<td width="46%">读出WriteFmtUserTypeStg()写入的信息。方便应用程序快速判断是否是它需要的格式数据。</td>
						</tr>
						<tr>
								<td width="16%">CreateStreamOnHGlobal()</td>
								<td width="46%">内存句柄 HGLOBAL 转换为流对象</td>
						</tr>
						<tr>
								<td width="16%">GetHGlobalFromStream()</td>
								<td width="46%">取得CreateStreamOnHGlobal()调用中使用的内存句柄</td>
						</tr>
				</tbody>
		</table>
		<p>为了让大家快速地浏览和掌握基本方法，上面所列表的函数并不是全部，我省略了“事务”函数和未实现函数部分。更全面的介绍，请阅读 MSDN。<br />    下面程序片段，演示了一些基本函数功能和调用方法。  <br />示例一：建立一个复合文件，并在其下建立一个子存储，在该子存储中再建立一个流，写入数据。</p>
		<pre>void SampleCreateDoc()
{
	::CoInitialize(NULL);	// COM 初始化
				// 如果是MFC程序，可以使用AfxOleInit()替代

	HRESULT hr;		// 函数执行返回值
	IStorage *pStg = NULL;	// 根存储接口指针
	IStorage *pSub = NULL;	// 子存储接口指针
	IStream *pStm = NULL;	// 流接口指针

	hr = ::StgCreateDocfile(	// 建立复合文件
		L"c:\\a.stg",	// 文件名称
		STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,	// 打开方式
		0,		// 保留参数
		&amp;pStg);		// 取得根存储接口指针
	ASSERT( SUCCEEDED(hr) );	// 为了突出重点，简化程序结构，所以使用了断言。
				// 在实际的程序中则要使用条件判断和异常处理

	hr = pStg-&gt;CreateStorage(	// 建立子存储
		L"SubStg",	// 子存储名称
		STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
		0,0,
		&amp;pSub);		// 取得子存储接口指针
	ASSERT( SUCCEEDED(hr) );

	hr = pSub-&gt;CreateStream(	// 建立流
		L"Stm",		// 流名称
		STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
		0,0,
		&amp;pStm);		// 取得流接口指针
	ASSERT( SUCCEEDED(hr) );

	hr = pStm-&gt;Write(		// 向流中写入数据
		"Hello",		// 数据地址
		5,		// 字节长度(注意，没有写入字符串结尾的\0)
		NULL);		// 不需要得到实际写入的字节长度
	ASSERT( SUCCEEDED(hr) );

	if( pStm )	pStm-&gt;Release();// 释放流指针
	if( pSub )	pSub-&gt;Release();// 释放子存储指针
	if( pStg )	pStg-&gt;Release();// 释放根存储指针

	::CoUninitialize()		// COM 释放
				// 如果使用 AfxOleInit(),则不调用该函数
}</pre>
		<img style="CURSOR: pointer" onclick="javascript:window.open(this.src);" src="http://www.vckbase.com/document/journal/vckbase43/images/stmtutpic2.jpg" onload="javascript:if(this.width&gt;500){this.resized=true;this.style.width=500;}" border="0" />
		<br />图二、运行示例程序一后，使用 DFView.exe 打开观察复合文件的效果图<br /><br />示例二：打开一个复合文件，<strong>枚举其根存储下的所有对象</strong>。<br />---(<strong>自己的理解,是不是每个对象就是一个子存储??)</strong><pre>＃i nclude <atlconv.h>	// ANSI、MBCS、UNICODE 转换

void SampleEnum() 
{	// 假设你已经做过 COM 初始化了

	LPCTSTR lpFileName = _T( "c:\\a.stg" );
	HRESULT hr;
	IStorage *pStg = NULL;
	
	USES_CONVERSION;				// （注6）
	LPCOLESTR lpwFileName = T2COLE( lpFileName );	// 转换T类型为宽字符
	hr = ::StgIsStorageFile( lpwFileName );	// 是复合文件吗？
	if( FAILED(hr) )	return;

	hr = ::StgOpenStorage(			// 打开复合文件
		lpwFileName,			// 文件名称
		NULL,
		STGM_READ | STGM_SHARE_DENY_WRITE,
		0,
		0,
		&amp;pStg);				// 得到根存储接口指针

	IEnumSTATSTG *pEnum=NULL;	// 枚举器
	hr = pStg-&gt;EnumElements( 0, NULL, 0, &amp;pEnum );
	ASSERT( SUCCEEDED(hr) );

	STATSTG statstg;
	while( NOERROR == pEnum-&gt;Next( 1, &amp;statstg, NULL) )
	{
		// statstg.type 保存着对象类型 STGTY_STREAM 或 STGTY_STORAGE
		// statstg.pwcsName 保存着对象名称
		// ...... 还有时间，长度等很多信息。请查看 MSDN

		::CoTaskMemFree( statstg.pwcsName );	// 释放名称所使用的内存（注6）
	}
	
	if( pEnum )	pEnum-&gt;Release();
	if( pStg )	pStg-&gt;Release();
}</atlconv.h></pre><b>六、小结</b><br /><br />　　<strong>复合文件，结构化存储，是微软组件思想的起源，在此基础上继续发展出了持续性、命名、ActiveX、对象嵌入、现场激活......一系列的新技术、新概念</strong>。因此理解<atlconv.h>和<atlconv.h>掌握 复合文件是非常重要的，即使在你的程序中并没有全面使用组件技术，复合文件技术也是可以单独被应用的。祝大家学习快乐，为社会主义软件事业而奋斗:-)<br /><br />留作业啦......<br />作业1：写个小应用程序，从 MSWORD 的 doc 文件中，提取出附加信息（作者、公司......）。<br />作业2：写个全功能的“复合文件浏览编辑器”。<br /><br />注1：踅摸(xuemo)，动词，北方方言，寻找搜索的意思。<br />注2：问：为什么不上网查资料学习？<br />     答：开什么国际玩笑！在那遥远的1995年代，我的500块工资，不吃不喝正好够上100小时的Internet网。<br />注3：OLE，对象的连接与嵌入。<br />注4：可以用 DFView.exe 打开 MSWORD 的 DOC 文件进行复合文件的浏览。但是该程序并没有实现国际化，不能打开中文文件名的复合文件，因此需要改名后才能浏览。<br />注5：CLSID，在后续的文章中介绍。<br />注6：关于 COM 中内存使用的问题，在后续的文章中介绍。 </atlconv.h></atlconv.h><img src ="http://www.cppblog.com/mydriverc/aggbug/29061.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mydriverc/" target="_blank">旅途</a> 2007-07-31 10:20 <a href="http://www.cppblog.com/mydriverc/articles/29061.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>结构或大内存块打包的办法</title><link>http://www.cppblog.com/mydriverc/articles/29027.html</link><dc:creator>旅途</dc:creator><author>旅途</author><pubDate>Mon, 30 Jul 2007 08:51:00 GMT</pubDate><guid>http://www.cppblog.com/mydriverc/articles/29027.html</guid><wfw:comment>http://www.cppblog.com/mydriverc/comments/29027.html</wfw:comment><comments>http://www.cppblog.com/mydriverc/articles/29027.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mydriverc/comments/commentRss/29027.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mydriverc/services/trackbacks/29027.html</trackback:ping><description><![CDATA[<p>结构或大内存块打包的办法</p>
<p>Revision History:</p>
<p>Version Date Creator Description<br>&nbsp;<br><br>Implementation Scope：</p>
<p>继续阅读之前，我们假设您熟悉以下知识：</p>
<ul>
    <li>SAFEARRAY
    <li>ISTREAM
    <li>Microsoft MSMQ</li>
</ul>
<p>目录：</p>
<p>1:概述</p>
<p>2:借用SAFEARRAY打包把结构写入MSMQ队列</p>
<p>3:借用IStream流打包传递数据到MSMQ队列</p>
<p>1.概述</p>
<p>通常我们建议通过MSMQ传递基于XML的字符串，但有时候也需要传递一些结构或者一些接口指针，那么如何打包传递呢？</p>
<p>这实际上可以转换为一个普适问题：</p>
<p>如何把一个结构体(structure object)或者巨大内存块（比如5MB左右）打包为PROPVARIANT-compatible的类型？</p>
<p>&nbsp;</p>
<p>首先，IMSMQMessagePtr的Body属性接收_variant_t参数:</p>
<p>inline void IMSMQMessage::PutBody ( const _variant_t &amp; pvarBody ) {</p>
<p>&nbsp;&nbsp;&nbsp; HRESULT _hr = put_Body(pvarBody);</p>
<p>&nbsp;&nbsp;&nbsp; if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));</p>
<p>}</p>
<p>如果我们想把结构作为消息的Body写入MSMQ消息队列，我们需要把我们的结构、大内存块或接口指针转换为_variant_t。</p>
<p>2.借用SAFEARRAY打包把结构写入MSMQ队列</p>
<p>把一个结构体打包为PROPVARIANT-compatible的类型，需要用到SAFEARRAY，一个带有边界信息的数组。这是一个常用技巧，很多文章都有提及，我就不多解释了。</p>
<p>但是，注意这种方式一次只能打包65536字节以下的数据，这是由于</p>
<p>SAFEARRAY* SafeArrayCreateVector( </p>
<p>&nbsp; VARTYPE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vt,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp; long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lLbound,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp; unsigned int&nbsp; cElements);</p>
<p>的定义所限制的。</p>
<p>我们通常会用SafeArrayCreateVector API创建一个单维SAFEARRAY，分配一个sizeof(_DATA)大小的连续内存块，而这个函数的第三个参数是一个unsigned int类型，所以最大值就只能是65536了。</p>
<p>更多SAFEARRAY知识，参见使用SAFEARRAY传递对象。</p>
<p>&nbsp;<br>2.借用SAFEARRAY打包把结构写入MSMQ队列</p>
<p>续上1.1篇的打包步骤（VC++代码）：</p>
<p>// ChangeStruct2Var函数的定义：</p>
<p>// 第一个参数：</p>
<p>//&nbsp;&nbsp; 类型：CComVariant</p>
<p>//&nbsp;&nbsp; 作用：接收者</p>
<p>// 第二个参数：</p>
<p>//&nbsp;&nbsp; 类型：_DATA*</p>
<p>//&nbsp;&nbsp; 作用：源</p>
<p>HRESULT ChangeStruct2Variant (CComVariant &amp;var, _DATA *pData)</p>
<p>{</p>
<p>HRESULT hr = S_OK;</p>
<p>&nbsp;</p>
<p>// 使用SafeArrayCreateVector API创建一个单维SAFEARRAY，分配一个sizeof(_DATA)大小的连续内存块</p>
<p>// VT--UI1代表非负整形的变量类型，1个字节</p>
<p>// 常数'0'定义数组的下界</p>
<p>LPSAFEARRAY lpsa = SafeArrayCreateVector(VT_UI1, 0, sizeof(_DATA));</p>
<p>LPBYTE pbData = NULL;</p>
<p>&nbsp;</p>
<p>if (lpsa)</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; //在你访问SAFEARRAY数据之前，你必须调用SafeArrayAccessData。该函数锁定数据并且返回一个指针。在这里，锁定数组意味着增加该数组的内部计数器（cLocks）</p>
<p>&nbsp;&nbsp;&nbsp; hr = SafeArrayAccessData(lpsa, (void **)&amp;pbData);</p>
<p>}</p>
<p>else</p>
<p>&nbsp;&nbsp;&nbsp; hr = HRESULT_FROM_WIN32(GetLastError());</p>
<p>&nbsp;</p>
<p>if (SUCCEEDED(hr))</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // 使用safe array：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // 将传入的_DATA指针指向的内存复制到pbData</p>
<p>&nbsp;&nbsp;&nbsp; CopyMemory(pbData, pData, sizeof(*pData));</p>
<p>// 设置var的类型为数组</p>
<p>&nbsp;&nbsp;&nbsp; var.vt = VT_ARRAYVT_UI1;</p>
<p>// 将var和我们的单维SAFEARRAY拉上关系：</p>
<p>&nbsp;&nbsp;&nbsp; var.parray = lpsa;</p>
<p>}</p>
<p>&nbsp;</p>
<p>if (pbData)</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; //相应用来释放数据的函数是SafeArrayUnaccessData()，该功能释放该参数的计数。</p>
<p>&nbsp;&nbsp;&nbsp; SafeArrayUnaccessData(var.parray);</p>
<p>}</p>
<p>if (FAILED(hr))</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // 销毁SAFEARRAY</p>
<p>&nbsp;&nbsp;&nbsp; SafeArrayDestroy(lpsa);</p>
<p>}</p>
<p>&nbsp;</p>
<p>return hr;</p>
<p>}</p>
<p>&nbsp;</p>
<p>////////////////////////////////////////////////////////////</p>
<p>//Added Headers:</p>
<p>////////////////////////////////////////////////////////////</p>
<p>#include &lt;comdef.h&gt;</p>
<p>#include &lt;atlbase.h&gt;</p>
<p>///////////////////////////////////////////////////////////</p>
<p>//Added for MSMQ:</p>
<p>///////////////////////////////////////////////////////////</p>
<p>#import "mqoa.dll" no_namespace, named_guids</p>
<p>typedef&nbsp;&nbsp;&nbsp; struct&nbsp; _DATA&nbsp; </p>
<p>{&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp; _n;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char&nbsp;&nbsp; _str;</p>
<p>}_DATA;</p>
<p>//main:</p>
<p>{</p>
<p>.. ..</p>
<p>.. ..</p>
<p>IMSMQMessagePtr pisMsg = NULL;</p>
<p>hr = pisMsg.CreateInstance("MSMQ.MSMQMessage");</p>
<p>_DATA msg;</p>
<p>msg._n = 1;</p>
<p>msg._str = '1';</p>
<p>CComVariant var;</p>
<p>// 打包函数：</p>
<p>ChangeStruct2Variant(var, &amp;msg);</p>
<p>// 打包后的CComVariant传递给MSMQMessege的Body属性：</p>
<p>pisMsg-&gt;Body= var;</p>
<p>pisMsg-&gt;AppSpecific=-1;</p>
<p>// 发送到消息队列：</p>
<p>pisMsg-&gt;Send(pisQueue);</p>
<p>.. ..</p>
<p>}<br>&nbsp;</p>
<p>&nbsp;</p>
<p>这样，就可以成功地把一个结构递交到MSMQ队列中了。</p>
<p>&nbsp;</p>
<p>Implementation Scope：<br>继续阅读之前，我们假设您熟悉以下知识：</p>
<ul>
    <li>SAFEARRAY
    <li>ISTREAM
    <li>Microsoft MSMQ</li>
</ul>
<p>目录：</p>
<p>1:概述</p>
<p>2:借用SAFEARRAY打包把结构写入MSMQ队列</p>
<p>3:借用IStream流打包传递数据到MSMQ队列</p>
<p>&nbsp;</p>
<p>下面给出读取MSMQ消息时解析的步骤（VC++代码）：</p>
<p>////////////////////////////////////////////////////////////</p>
<p>//Added Headers:</p>
<p>////////////////////////////////////////////////////////////</p>
<p>#include &lt;comdef.h&gt;</p>
<p>#include &lt;atlbase.h&gt;</p>
<p>///////////////////////////////////////////////////////////</p>
<p>//Added for MSMQ:</p>
<p>///////////////////////////////////////////////////////////</p>
<p>#import "mqoa.dll" no_namespace, named_guids</p>
<p>typedef&nbsp;&nbsp;&nbsp; struct&nbsp; _DATA&nbsp; </p>
<p>{&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp; _n;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char&nbsp;&nbsp; _str;</p>
<p>}_DATA;</p>
<p>//main:</p>
<p>{</p>
<p>.. ..</p>
<p>.. ..</p>
<p>hr = pisQI-&gt;raw_Open(MQ_PEEK_ACCESS,MQ_DENY_NONE,&amp;pisQueue);</p>
<p>IMSMQMessagePtr piMessage;</p>
<p>// 获取MSMQ队列中的一个消息：</p>
<p>piMessage = pisQueue-&gt;PeekCurrent();</p>
<p>_DATA *msg = new _DATA();</p>
<p>// 解析函数：</p>
<p>ChangeVariant2Struct(CComVariant(piMessage-&gt;Body), msg);</p>
<p>.. ..</p>
<p>}</p>
<p>&nbsp;</p>
<p>// ChangeVariant2Struct函数的定义：</p>
<p>// 第一个参数：</p>
<p>//&nbsp;&nbsp; 类型：CComVariant</p>
<p>//&nbsp;&nbsp; 作用：源</p>
<p>// 第二个参数：</p>
<p>//&nbsp;&nbsp; 类型：_DATA*</p>
<p>//&nbsp;&nbsp; 作用：接收者</p>
<p>HRESULT ChangeVariant2Struct (CComVariant &amp;var, _DATA *DP)</p>
<p>{</p>
<p>SAFEARRAY* psa;</p>
<p>BYTE HUGEP *lpb;</p>
<p>psa = var.parray;</p>
<p>SafeArrayAccessData(psa, (void HUGEP **)&amp;lpb);</p>
<p>CopyMemory((LPVOID)DP, (LPVOID)lpb, 8);</p>
<p>SafeArrayUnaccessData(psa);</p>
<p>&nbsp;</p>
<p>return S_OK;</p>
<p>}<br>&nbsp;</p>
<p><br>Writen by zhengyun.NoJunk(at)tomosoft.dot.com</p>
<p>Disclaimers：<br>&nbsp;</p>
<p>本文档仅供参考。本文档所包含的信息代表了在发布之日，zhengyun对所讨论问题的当前看法，zhengyun不保证所给信息在发布之日以后的准确性。 </p>
<p>用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档，但须遵守以下条款： </p>
<p>复制时不得修改原文，复制内容须包含所有页 ； <br>所有副本均须含有 zhengyun的版权声明以及所提供的其它声明 ；</p>
<p><br>Implementation Scope：<br>继续阅读之前，我们假设您熟悉以下知识：</p>
<ul>
    <li>SAFEARRAY
    <li>ISTREAM
    <li>Microsoft MSMQ</li>
</ul>
<p>目录：</p>
<p>1:概述</p>
<p>2:借用SAFEARRAY打包把结构写入MSMQ队列</p>
<p>3:借用IStream流打包传递数据到MSMQ队列</p>
<p>&nbsp;</p>
<p>3.借用IStream流传递数据<br>正如前面所述，当你有一块非常巨大的数据要传递给MSMQ队列时，而且你希望一次液压成型，那么把它打包入IStream流，也是一个很常用技巧，了解COM的人都知道，我也不多解释了。</p>
<p>我们研究了ATL中IPersistMemoryImpl接口Load方法的实现机理，来做我们的事情：</p>
<p>// 函数名：LoadStreamOnHugeMemory</p>
<p>// 功能：&nbsp; 同上</p>
<p>// 第一个参数pvMem指向一块要打包的内存，第二个参数指明这块内存的大小</p>
<p>HRESULT LoadStreamOnHugeMemory(void pvMem, ULONG cbSize)</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // Get Memory Handle:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, cbSize);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; If(NULL == h) return E_OUTOFMEMORY;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; LPVOID pv = GlobalLock(h);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; If(!pv) return E_OUTOFMEMORY;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // Copy to memory block</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; CopyMemory(pv, pvMem, cbSize);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; CComPtr&lt;IStream&gt; spStream;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // Create stream on Memory:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; HRESULT hr = CreateStreamOHGlobal(h, TRUE, &amp;spStream);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; If(FAILED(hr)) </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GlobalUnlock(h);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GlobalFree(h);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return hr;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // stream now owns the memory</p>
<p>&nbsp;</p>
<p>// unlock the data</p>
<p>GlobalUnlock(hGlobal);</p>
<p>&nbsp;</p>
<p>// Create a stream holder. Load the stream holder from the global stream.</p>
<p>//&nbsp; THIS STREAM HOLDER IS INTERITED FROM IPersistStream</p>
<p>//&nbsp; And all virtual functions are Modified to handle the object....</p>
<p>CComPtr &lt;IStreamHolder&gt; pHolder = new CComObject &lt;CStreamHolder&gt;;</p>
<p>&nbsp;</p>
<p>CComPtr &lt;IPersistStream&gt; pHolderStream;</p>
<p>hr = pHolder-&gt;QueryInterface (IID_IPersistStream, (void **)&amp;pHolderStream);</p>
<p>&nbsp;</p>
<p>pStream-&gt;Seek( zero, STREAM_SEEK_SET, NULL );</p>
<p>pHolderStream-&gt;Load(pStream);</p>
<p>&nbsp;</p>
<p>CComVariant vComData = pHolder;</p>
<p>.. ..</p>
<p>&nbsp;</p>
<p>//</p>
<p>// now, you have a big chunk of memory loaded into a ComVariant</p>
<p>//</p>
<p>// 现在你可以把打包后的CComVariant传递给MSMQMessege的Body属性了：</p>
<p>pisMsg-&gt;Body = vComData;</p>
<p>.. ..</p>
<p>}<br>&nbsp;</p>
<p>&nbsp;</p>
<p>其实，Aydin的实现和ATL中IPersistMemoryImpl接口Load方法实现异曲同工。我们不妨换一种方式实现。</p>
<p>&nbsp;</p>
<p>Writen by zhengyun.NoJunk(at)tomosoft.dot.com</p>
<p>Disclaimers：<br>本文档仅供参考。本文档所包含的信息代表了在发布之日，zhengyun对所讨论问题的当前看法，zhengyun不保证所给信息在发布之日以后的准确性。 </p>
<p>用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档，但须遵守以下条款： </p>
<p>复制时不得修改原文，复制内容须包含所有页 ； <br>所有副本均须含有 zhengyun的版权声明以及所提供的其它声明 ； <br>不得以赢利为目的对本文档进行传播 。</p>
<p>&nbsp;</p>
<p>Implementation Scope：<br>继续阅读之前，我们假设您熟悉以下知识：</p>
<ul>
    <li>SAFEARRAY
    <li>ISTREAM
    <li>Microsoft MSMQ</li>
</ul>
<p>目录：</p>
<p>1:概述</p>
<p>2:借用SAFEARRAY打包把结构写入MSMQ队列</p>
<p>3:借用IStream流打包传递数据到MSMQ队列</p>
<p>&nbsp;</p>
<p>其实，Aydin的实现和ATL中IPersistMemoryImpl接口Load方法实现异曲同工。我们不妨换一种方式实现。</p>
<p>Aydin给出的VC++代码是：</p>
<p>// 智能流指针</p>
<p>CComPtr&lt;IStream&gt; pStream = NULL;</p>
<p>LARGE_INTEGER zero = {0,0};</p>
<p>// hGlobal是内存句柄：</p>
<p>LPBYTE pChunk = (BYTE *) GlobalLock(hGlobal);</p>
<p>&nbsp;</p>
<p>// 创建一个空的stream：</p>
<p>HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &amp;pStream );</p>
<p>pStream-&gt;Seek( zero, STREAM_SEEK_SET, NULL );</p>
<p>&nbsp;</p>
<p>ULONG pcbWritten = 0;</p>
<p>// pChunk现在已经指向我们的巨大内存块；</p>
<p>// 我们把这块内存写入IStream流中：</p>
<p>pStream-&gt;Write (pChunk, dwNumRead, &amp;pcbWritten);</p>
<p>// 检查是否全部写入了：</p>
<p>ATLASSERT(pcbWritten==dwNumRead);</p>
<p>&nbsp;</p>
<p>// unlock the data</p>
<p>GlobalUnlock(hGlobal);</p>
<p>&nbsp;</p>
<p>// 剩下的一样，也是让CComPtr &lt;IPersistStream&gt;来调用Load方法加载IStream</p>
<p>&nbsp;</p>
<p>// Create a stream holder. Load the stream holder from the global stream.</p>
<p>//&nbsp; THIS STREAM HOLDER IS INTERITED FROM IPersistStream</p>
<p>//&nbsp; And all virtual functions are Modified to handle the object....</p>
<p>CComPtr &lt;IStreamHolder&gt; pHolder = new CComObject &lt;CStreamHolder&gt;;</p>
<p>&nbsp;</p>
<p>CComPtr &lt;IPersistStream&gt; pHolderStream;</p>
<p>hr = pHolder-&gt;QueryInterface (IID_IPersistStream, (void **)&amp;pHolderStream);</p>
<p>&nbsp;</p>
<p>pStream-&gt;Seek( zero, STREAM_SEEK_SET, NULL );</p>
<p>pHolderStream-&gt;Load(pStream);</p>
<p>&nbsp;</p>
<p>CComVariant vComData = pHolder;</p>
<p>.. ..</p>
<p>&nbsp;</p>
<p>//</p>
<p>// now, you have a big chunk of memory loaded into a ComVariant</p>
<p>//</p>
<p>// 现在你可以把打包后的CComVariant传递给MSMQMessege的Body属性了：</p>
<p>pisMsg-&gt;Body = vComData;</p>
<p>.. ..</p>
<p>&nbsp;</p>
<p>//Coder: Aydin T.BAKIR<br>&nbsp;</p>
<p><br>&nbsp;全文完。</p>
<p>Writen by zhengyun.NoJunk(at)tomosoft.dot.com</p>
<p>Disclaimers：<br>&nbsp;</p>
<p>本文档仅供参考。本文档所包含的信息代表了在发布之日，zhengyun对所讨论问题的当前看法，zhengyun不保证所给信息在发布之日以后的准确性。 </p>
<p>用户应清楚本文档的准确性及其使用可能带来的全部风险。可以复制和传播本文档，但须遵守以下条款： </p>
<p>复制时不得修改原文，复制内容须包含所有页 ； <br>所有副本均须含有 zhengyun的版权声明以及所提供的其它声明 ； <br>不得以赢利为目的对本文档进行传播 。</p>
<img src ="http://www.cppblog.com/mydriverc/aggbug/29027.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mydriverc/" target="_blank">旅途</a> 2007-07-30 16:51 <a href="http://www.cppblog.com/mydriverc/articles/29027.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows区对象(Bands)的创建与定制</title><link>http://www.cppblog.com/mydriverc/articles/29022.html</link><dc:creator>旅途</dc:creator><author>旅途</author><pubDate>Mon, 30 Jul 2007 08:20:00 GMT</pubDate><guid>http://www.cppblog.com/mydriverc/articles/29022.html</guid><wfw:comment>http://www.cppblog.com/mydriverc/comments/29022.html</wfw:comment><comments>http://www.cppblog.com/mydriverc/articles/29022.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mydriverc/comments/commentRss/29022.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mydriverc/services/trackbacks/29022.html</trackback:ping><description><![CDATA[<table cellSpacing=0 cellPadding=0 width=755 align=center border=0>
    <tbody>
        <tr>
            <td height=74>
            <p align=center>&nbsp;</p>
            <p align=center><strong><font color=#009900>Windows区对象(Bands)的创建与定制</font></strong><br>编译/<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#97;&#110;&#103;&#119;&#105;&#114;&#101;&#64;&#115;&#105;&#110;&#97;&#46;&#99;&#111;&#109;"><u><font color=#0000ff>赵湘宁</font></u></a> </p>
            <p><a href="http://www.vckbase.com/document/journal/vckbase11/src/BandObjs.zip"><u><font color=#0000ff>本文例子代码<br></font></u></a><br></p>
            <table cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td><strong>1 <a href="http://www.westimpi.com.cn/school/win1.htm#%D2%BB%A1%A2%20%BC%F2%BD%E9"><u><font color=#800080>简介</font></u></a></strong> <strong><br>&nbsp;&nbsp;1.1<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#%E4%AF%C0%C0%C0%B8%C7%F8%B6%D4%CF%F3"><u><font color=#800080>浏览栏区对象</font></u></a><br>&nbsp;&nbsp;1.2<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#工具栏区对象"><u><font color=#800080>工具栏区对象</font></u></a></strong> <strong><br>&nbsp;&nbsp;1.3<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#桌面区对象"><u><font color=#800080>桌面区对象</font></u></a></strong> <strong><br>2 <a href="http://www.westimpi.com.cn/school/win1.htm#二、实现区对象"><u><font color=#800080>实现区对象</font></u></a> <br>&nbsp;&nbsp;2.1<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#howtoreg"><u><font color=#800080>注册</font></u></a></strong><br><strong>3 <a href="http://www.westimpi.com.cn/school/win1.htm#sample1"><u><font color=#800080>一个简单的例子</font></u></a></strong> <br><strong>&nbsp;&nbsp;3.1<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#DLL函数"><u><font color=#800080>DLL函数</font></u></a></strong> <br><strong>&nbsp;&nbsp;3.2<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#注册定制的浏览栏"><u><font color=#800080>注册定制的浏览栏</font></u></a></strong> <br><strong>&nbsp;&nbsp;3.3<em> </em><a href="http://www.westimpi.com.cn/school/win1.htm#必须实现的接口"><u><font color=#800080>必须实现的接口</font></u></a></strong><br><strong>&nbsp;&nbsp;&nbsp;&nbsp;3.3.1</strong><em> <a href="http://www.westimpi.com.cn/school/win1.htm#IUnknown"><u><font color=#800080>IUnknown</font></u></a></em> <br><strong>&nbsp;&nbsp;&nbsp;&nbsp;3.3.2 </strong><em><a href="http://www.westimpi.com.cn/school/win1.htm#IObjectWithSite接口"><u><font color=#800080>IObjectWithSite</font></u></a></em><br><strong>&nbsp;&nbsp;&nbsp;&nbsp;3.3.3 </strong><em><a href="http://www.westimpi.com.cn/school/win1.htm#IPersistStream接口"><u><font color=#800080>IPersistStream</font></u></a></em> <br><strong>&nbsp;&nbsp;&nbsp;&nbsp;3.3.4 </strong><em><a href="http://www.westimpi.com.cn/school/win1.htm#IDeskBand接口"><u><font color=#800080>IDeskBand</font></u></a></em> <br><strong>&nbsp;&nbsp;3.4 <a href="http://www.westimpi.com.cn/school/win1.htm#可选择的接口实现"><u><font color=#800080>可选择的接口实现</font></u></a></strong> <br><strong>&nbsp;&nbsp;&nbsp;&nbsp;3.4.1 </strong><a href="http://www.westimpi.com.cn/school/win1.htm#IInputObject接口"><u><font color=#800080>IInputObject</font></u></a> <br><strong><em>&nbsp;&nbsp;3.5 </em><a href="http://www.westimpi.com.cn/school/win1.htm#窗口过程"><u><font color=#800080>窗口过程</font></u></a></strong> <br><strong>4 <a href="http://www.westimpi.com.cn/school/win1.htm#四、总结"><u><font color=#800080>总结</font></u></a></strong><br><br><strong><a name="一、 简介">一、 简介</a></strong><br>Windows的区（Bands）对象有三种：既浏览栏（Explorer Bar）区对象，工具栏（Tools Bands）区对象，和桌面区对象（Desk Bands）。<br><br><em><strong><a name=浏览栏区对象>浏览栏区对象</a></strong></em><br>浏览栏区对象简称浏览栏，它是从IE4.0引入的，它是邻近浏览器窗格的一个显示区域。实际上它是IE窗口中的一个子窗口，可以用它来显示信息及与用户交互。浏览栏即可以是以垂直方式定位在浏览器窗格的左边。也可以水平方式定位在浏览器窗格下面。（如图一） </td>
                    </tr>
                </tbody>
            </table>
            <p><img height=390 src="http://www.westimpi.com.cn/school/win1.files/112700101.gif" width=504> </p>
            <table cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td>图一<br>在浏览栏中可以创建很多子菜单或选项，用户能以不同方式选择这些子菜单或选项提供的功能，打开IE或者资源管理器，从&#8220;查看&#8221;菜单中选择&#8220;浏览栏&#8221;，可以看到Windows提供了几种标准的浏览栏菜单，如&#8220;搜索（Search）&#8221;,&#8220;收藏夹（Favorites）&#8221;， 和&#8220;历史记录（History）&#8221;,以及&#8220;文件夹（All Folders）&#8221;。（如图二） </td>
                    </tr>
                </tbody>
            </table>
            <p><img height=320 src="http://www.westimpi.com.cn/school/win1.files/112700102.gif" width=467> </p>
            <table cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td>图二<br>为了创建定制的浏览栏，必须编程实现，然后注册它们。Windows在外壳（Shell）4.71中引入了区对象。它提供与普通窗口一样的功能。但因为它是以IE或外壳为容器的COM对象，所以实现起来就与普通窗口有所不同。图一中显示的就是一个简单的浏览栏例子。图中有一个垂直的浏览栏和一个水平的浏览栏。<br><br><strong><em><a name=工具栏区对象>工具栏区对象</a></em></strong><br>工具栏区对象简称工具栏，它是在IE5.0中引入用以支持单选工具栏（radio toolbar）特性的。IE工具栏实际上是一个Rebar控件，它包含了几个工具栏（toolbar）控件。通过创建工具栏，你可以将某个区对象功能添加到Rebar控件中。不论是在IE中还是在资源管理器中，区对象都是一样的，所以工具栏也是一个通用窗口。（如图三）<br><span lang=EN-US style="FONT-SIZE: 10.5pt; FONT-FAMILY: ; mso-bidi-font-size: 12.0pt" Times New Roman?;mso-fareast-font-family:宋体;mso-font-kerning: 1.0pt;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN;mso-bidi-language: AR-SA?><v:shapetype id=_x0000_t75 coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></v:path><o:lock v:ext="edit" aspectratio="t"></o:lock></v:shapetype><v:shape id=_x0000_i1025 type="#_x0000_t75" width:214.5pt; height:161.25pt??><v:imagedata src="file:///C:/windows/TEMP/msoclip1/01/clip_image001.png" o:title="myband3"></v:imagedata></v:shape><img height=215 src="http://www.westimpi.com.cn/school/win1.files/112700103.jpg" width=286 v:shapes="_x0000_i1025"></span> <br>图三<br><br>用户可以从&#8220;查看&#8221;菜单中的&#8220;工具栏&#8221;子菜单中选择显示单选工具栏，也可以在工具栏区域单击鼠标右键从它的上下文菜单中选择显示单选工具栏。<br><br><strong><em><a name=桌面区对象>桌面区对象</a></em></strong><br>区对象也可以用在桌面，也就是创建桌面区对象。虽然它们的基本实现与浏览栏类似，但桌面区与IE没有关系，它不用IE作为容器。它主要用来创建桌面浮动窗口。通过在任务栏上单击右键，然后在弹出的菜单中选择&#8220;工具栏&#8221;的子菜单选项。（如图四） <br><img height=293 src="http://www.westimpi.com.cn/school/win1.files/112700104.gif" width=343 border=0> <br>图四<br><br>桌面区的初始浮动位置在任务栏：（如图五<br><img height=103 src="http://www.westimpi.com.cn/school/win1.files/112700105.gif" width=532 border=0> <br>图五<br><br>用户可以将桌面区拖到桌面上，这时它就成了一个普通窗口：（如图六）<br><img height=127 src="http://www.westimpi.com.cn/school/win1.files/112700106.gif" width=480 border=0><br>图六<br></td>
                    </tr>
                </tbody>
            </table>
            <table cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td><strong><a name=二、实现区对象>二、实现区对象</a><br></strong>尽管可以像使用普通窗口一样使用区对象，但它们毕竟是COM对象，存在于某个容器之中。如浏览栏和工具栏位于IE之中，桌面区位于外壳之中。虽然它们的功能不同，但其基本实现非常相似。一个主要的差别是它们的注册方式不同，而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现细节可参考<a href="http://www.westimpi.com.cn/school/win1.htm#sample1" target=_self><u><font color=#800080>垂直浏览栏例子程序</font></u></a>。<br>区对象除了要实现 IUnknown 和 IClassFactory 两个接口之外，所有的区对象还必须实现以下这几个接口：<strong> </strong>
                        <ul>
                            <li>&nbsp;IDeskBand&nbsp;
                            <li>&nbsp;IObjectWithSite&nbsp;
                            <li>&nbsp;IPersistStream </li>
                        </ul>
                        另外，在注册时除了注册它们的CLSID之外，浏览栏和桌面区对象还必须进行组件类别（category）的注册。它决定了对象的类型及其容器。工具栏不需要进行种类注册。归纳起来，需要进行CATID注册的三种区对象是：<br>
                        <table height=64 width="100%" border=1>
                            <tbody>
                                <tr>
                                    <td align=middle width="50%" height=8><strong>区对象类型</strong></td>
                                    <td align=middle width="50%" height=8><strong>组件类型</strong></td>
                                </tr>
                                <tr>
                                    <td align=middle width="50%" height=1>垂直浏览栏</td>
                                    <td align=middle width="50%" height=1>CATID_InfoBand</td>
                                </tr>
                                <tr>
                                    <td align=middle width="50%" height=16>水平浏览栏</td>
                                    <td align=middle width="50%" height=16>CATID_CommBand</td>
                                </tr>
                                <tr>
                                    <td align=middle width="50%" height=16>桌面区</td>
                                    <td align=middle width="50%" height=16>CATID_DeskBand</td>
                                </tr>
                            </tbody>
                        </table>
                        <br>对于如何注册区对象的进一步讨论请参见<a href="http://www.westimpi.com.cn/school/win1.htm#howtoreg" target=_self><u><font color=#800080>注册部分</font></u></a>。<br>如果某个区对象接受用户输入，它还必须实现IInputObject接口。如果要往上下文菜单中添加菜单项目，还必须实现IContextMenu接口。注意：工具栏区对象不支持上下文菜单。<br>&nbsp;&nbsp;&nbsp; 因为区对象实现的是子窗口，所以它们还必须有窗口过程来处理Windows的消息。<br>&nbsp;&nbsp;&nbsp; 区对象可以通过其IOleCommandTarget接口发送命令到它的容器。为了得到这个接口的指针，必须调用容器的IInputObjectSite::QueryInterface方法<xxxxime xime="T">来</xxxxime>请求IID_IoleCommandTarget。然后用IOleCommandTarget::Exec把命令发送到容器。命令组是CGID_DeskBand。当某个区对象的IDeskBand::GetBandInfo方法被调用时，容器用dwBandID参数将一个标示符赋给这个对象。这个标示符被用于IOleCommandTarget::Exec方法调用时所用命令组中的三个命令。目前命令组共支持四个IOleCommandTarget::Exec命令IDs。这四个命令的解释如下：<br>DBID_BANDINFOCHANGED——Band的信息已改变。参数pvaIn的值应该是最近一次调用所用的band标示符。容器将调用这个标示符所指的band对象的IDeskBand::GetBandInfo方法请求更新的信息。<br>DBID_MAXIMIZEBAND——容器将最大化band。参数pvaIn的值应该是最近一次调用所用的band标示符。<br>DBID_SHOWONLY——关闭或打开容器中其它band。参数pvaIn的值为VT_UNKNOWN类型，可以取下列值之一：<br>
                        <table width="100%" border=1>
                            <tbody>
                                <tr>
                                    <td vAlign=top align=middle width="24%">值</td>
                                    <td width="76%">描述</td>
                                </tr>
                                <tr>
                                    <td vAlign=top align=middle width="24%">pUnk</td>
                                    <td width="76%">这个对象IUnknown接口的指针。所有其它的桌面band将被隐藏。</td>
                                </tr>
                                <tr>
                                    <td vAlign=top align=middle width="24%">0</td>
                                    <td width="76%">隐藏所有桌面band。</td>
                                </tr>
                                <tr>
                                    <td vAlign=top align=middle width="24%">1</td>
                                    <td width="76%">显示所有桌面band。</td>
                                </tr>
                            </tbody>
                        </table>
                        DBID_PUSHCHEVRON——目前没有实现。<br><br><a name=howtoreg><strong><em>注册</em></strong></a><br>区对象必须作为进程内服务器（in-process）注册。其线程模型必须为&#8220;Apartment&#8221;。也就是说区对象必须以DLL的形式来实现。用来描述服务器注册条目的缺省值是一个菜单文本串。就拿浏览栏来说。这个菜单出现在资源管理器或IE &#8220;查看（View）&#8221;菜单的&#8220;浏览栏（Explorer Bar）&#8221;子菜单中。而工具栏的菜单则出现在资源管理器或IE &#8220;查看（View）&#8221;菜单的&#8220;工具栏（Toolbars）&#8221;子菜单中。桌面区出现在任务栏上下文菜单的&#8220;工具栏（Toolbars）&#8221;子菜单中。作为菜单资源，提供键盘快捷的方法与一般菜单快捷键相同。也就是将&#8220;&amp;&#8221;字符放在某个单词字母前表示这个字母显示下划线来指示快捷键。<br>通常区对象的注册条目如下:<br>
                        <pre>HKEY_CLASSES_ROOT
                        ...
                        CLSID
                        ...
                        {Band 对象的 CLSID GUID} = "菜单文本串"
                        InProcServer32 = "DLL 路径名"
                        ThreadingModel = "Apartment"</pre>
                        工具栏区对象必须还要注册对象的CLSID。为此必须在HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar下创建一个REG_SZ值，用工具栏区对象的CLSID GUID串命名。如：<br>
                        <pre>HKEY_LOCAL_MACHINE
                        Software
                        Microsoft
                        Internet Explorer
                        Toolbar
                        { Band 对象的 CLSID GUID }</pre>
                        <br>除此之外，还有几个可选的注册值可以加到注册表中，本文的例子中未使用这些值。 <br>
                        <ul>
                            <li>HKEY_CLASSES_ROOT\CLSID\{Band 对象的 CLSID GUID}\Instance\CLSID, 它应该被设置为 "{4D5C8C2A-D075-11D0-B416-00C04FB90376}".&nbsp;<br>
                            <li>HKEY_CLASSES_ROOT\CLSID\{Band对象的CLSID GUID}\Instance\InitPropertyBag\Url 它应该被设置为要在浏览栏显示的包含HTML内容的文件位置。<br>
                            <li>\HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Explorer Bars\{Band 对象的 CLSID GUID}\BarSize 它应该被设置为栏目的高和宽，它需要八个字节才能作为串放入注册表，字节之间用逗号分开。开始的四个字节一像素为单位指定大小，格式要用十六进制，从最左边字节开始。最后四个字节是保留字节，应该将它置为零。例如，垂直浏览栏的缺省宽度为291（0x123）像素，则BarSize 的值应该是"23,01,00,00,00,00,00,00"&nbsp; </li>
                        </ul>
                        如果要用浏览栏显示HTML，则前两个注册项是必须的。最后一个注册项则根据垂直的或者水平的浏览栏定义相应的缺省宽度和高度。<br>能显示HTML的浏览栏（缺省宽度为291各像素单位）注册表条目的形式如下：<br>
                        <pre>HKEY_CLASSES_ROOT
                        ...
                        CLSID
                        ...
                        {Band 对象的 CLSID GUID} = "菜单文本串"
                        InProcServer32 = "DLL 路径名"
                        ThreadingModel = "Apartment"
                        Instance
                        CLSID = "{4D5C8C2A-D075-11D0-B416-00C04FB90376}"
                        InitPropertyBag
                        Url = "HTML文件"
                        ...
                        HKEY_CURRENT_USER
                        ...
                        Software
                        ...
                        Microsoft
                        ...
                        Internet Explorer
                        ...
                        Explorer Bars
                        { Band 对象的 CLSID GUID }
                        BarSize = "23,01,00,00,00,00,00,00"</pre>
                        <br>你可以通过编程的方式来处理区对象类别 CATID 的注册。创建一个组件类别管理器对象(CLSID_StdComponentCategoriesMgr)并请求一个指向ICatRegister接口的指针。将区对象的CLSID和CATID传递到ICatRegister::RegisterClassImplCategories。<br><strong><a name=sample1>三、定制浏览栏的一个简单例子</a></strong><br>这个例子展示了前面所介绍过的垂直浏览栏的整个实现过程。它借助了平台SDK（Platform SDK——在msdn中可以找到）中关于band对象示范代码。其中还包括了水平浏览栏和桌面band的实现代码。详细实现细节请参见：CommBand.cpp和DeskBand.cpp。<br>创建定制浏览栏的基本过程是这样的：<br>
                        <ol>
                            <li>实现DLL需要的函数。
                            <li>实现必须的COM接口。
                            <li>实现任何想要的可选接口。
                            <li>注册对象的CLSID。
                            <li>进行恰当的组件种类注册。
                            <li>创建IE子窗口，调整窗口大小适合浏览栏的显示区域。
                            <li>使用子窗口显示信息并与用户交互。 </li>
                        </ol>
                        实际上，只要通过恰当的组件种类注册，浏览栏例子代码便既可用于浏览栏的实现，也能用于桌面band实现。更加复杂的实现将需要定制每种对象类型的显示区域和容器。但大多数的定制工作都能通过范例代码以及Windows子窗口的编程技术来完成。例如，你可以添加用户交互控制或者进行色彩丰富的图形显示处理。<br><br><strong><em><a name=DLL函数>DLL函数</a></em></strong> <br>所有三种区对象被打包在一个DLL中，它输出以下的函数：<br>
                        <ul>
                            <li>DllMain
                            <li>DllCanUnloadNow&nbsp;
                            <li>DllGetClassObject&nbsp;
                            <li>DllRegisterServer&nbsp; </li>
                        </ul>
                        这些函数可以在BandObjs.cpp中找到，它们服务于所有三种区对象。前三个函数乃标准的实现，我们不再本文中讨论。类工厂也是标准实现，代码可以在ClsFact.cpp中找到<br><br><strong><em><a name=注册定制的浏览栏>注册定制的浏览栏</a></em></strong><br><br>有了COM对象后，必须对浏览栏的CLSID进行注册。另外如果要与IE或资源管理器<br>协调运行，还必须进行的恰当的组件种类（CATID_InfoBand）注册。这个工作由DllRegisterServer处理。浏览栏例子代码有关的处理部分如下：<br>
                        <pre>...
                        //注册浏览栏对象
                        if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直浏览栏例子")))
                        return SELFREG_E_CLASS;
                        //注册浏览栏的对象组件种类
                        if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand))
                        return SELFREG_E_CLASS;
                        ...</pre>
                        区对象的注册使用通常的COM过程，它由私有函数RegisterServer处理。<br>除了CLSID之外，这个区对象服务器还必须注册一个以上的组件种类。这实际上是垂直浏览栏和水平浏览栏实现之间的主要差别。这个过程的处理是通过创建一个组件种类管理器对象（CLSID_StdComponentCategoriesMgr），并用ICatRegister::RegisterClassImplCategories方法来注册区对象服务器。在这个例子中，组件种类注册的处理是通过将浏览栏的CLSID和CATID传递到私有函数RegisterComCat完成的：<br>
                        <pre>BOOL RegisterComCat(CLSID clsid, CATID CatID)
                        {
                        ICatRegister   *pcr;
                        HRESULT        hr = S_OK ;
                        CoInitialize(NULL);
                        hr = CoCreateInstance(  CLSID_StdComponentCategoriesMgr,
                        NULL,
                        CLSCTX_INPROC_SERVER,
                        IID_ICatRegister,
                        (LPVOID*)&amp;pcr);
                        if(SUCCEEDED(hr))
                        {
                        hr = pcr-&gt;RegisterClassImplCategories(clsid, 1, &amp;CatID);
                        pcr-&gt;Release();
                        }
                        CoUninitialize();
                        return SUCCEEDED(hr);
                        }</pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            <br>
            <table cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td><strong><em><a name=必须实现的接口>必须实现的接口</a><br></em></strong>垂直浏览栏例子实现了四个必须的接口：IUnknown, IObjectWithSite, IPersistStream, 和IDeskBand，它们都在CExplorerBar类中实现。<br><u><a name=IUnknown>IUnknown</a></u><br>构造函数，析构函数和IUnknown实现比较简单，本文在此不讨论。细节请参见源代码。<br><em><u><a name=IObjectWithSite接口>IObjectWithSite接口</a></u></em> <br>当用户选择某个浏览栏时，容器调用相应band对象的IObjectWithSite::SetSite方法。参数将被设置成这个现场（Site）的IUnknown指针。<br>通常，SetSite实现应该完成下列步骤：<br>
                        <ol>
                            <li>释放当前所把持的任何现场指针。
                            <li>如果传递到SetSite的指针被置为NULL，此则区对象被删除。SetSite可以返回S_OK。
                            <li>如果传递到SetSite的指针被置为非NULL，则建立新的现场。SetSite应该做以下的事情：<br></li>
                        </ol>
                        <ol type=i>
                            <li>调用现场QueryInterface方法请求IOleWindow接口。
                            <li>调用IOleWindow::GetWindow获取父窗口句柄，并存储它，以便以后使用。如果不再使用的话，就释放IOleWindow接口。
                            <li>创建此band对象的窗口为一个子窗口，其父窗口就是上一步获得的那个窗口。注意在此不能将它创建成可见窗口。
                            <li>如果此band对象实现IInputObject，调用现场QueryInterface方法请求IInputObjectSite接口，存储这个接口的指针以备后用。
                            <li>如果所有步骤都成功，则返回S_OK，否则返回OLE定义的错误代码以指示错误类型。 </li>
                        </ol>
                        以下是浏览栏实现SetSite的方法。m_pSite是私有成员变量，用它来保存IInputObjectSite指针，而m_hwndParent保存父窗口句柄。<br>
                        <pre>STDMETHODIMP CExplorerBar::SetSite(IUnknown* punkSite)
                        {
                        //如果某个现场被把持，则释放它
                        if(m_pSite)
                        {
                        m_pSite-&gt;Release();
                        m_pSite = NULL;
                        }
                        //如果punkSite 不为NULL, 建立一个新的现场
                        if(punkSite)
                        {
                        //获取父窗口
                        IOleWindow  *pOleWindow;
                        m_hwndParent = NULL;
                        if(SUCCEEDED(punkSite-&gt;QueryInterface(IID_IOleWindow, (LPVOID*)&amp;pOleWindow)))
                        {
                        pOleWindow-&gt;GetWindow(&amp;m_hwndParent);
                        pOleWindow-&gt;Release();
                        }
                        if(!m_hwndParent)
                        return E_FAIL;
                        if(!RegisterAndCreateWindow())
                        return E_FAIL;
                        //获取柄保存IInputObjectSite指针
                        if(SUCCEEDED(punkSite-&gt;QueryInterface(IID_IInputObjectSite, (LPVOID*)&amp;m_pSite)))
                        {
                        return S_OK;
                        }
                        return E_FAIL;
                        }
                        return S_OK;
                        }</pre>
                        这个例子的GetSite只简单地用SetSite保存的现场指针实现了对现场QueryInterface方法的调用。<br>
                        <pre>STDMETHODIMP CExplorerBar::GetSite(REFIID riid, LPVOID *ppvReturn)
                        {
                        *ppvReturn = NULL;
                        if(m_pSite)
                        return m_pSite-&gt;QueryInterface(riid, ppvReturn);
                        return E_FAIL;
                        }</pre>
                        窗口创建由私有方法RegisterAndCreateWindow负责。如果这个窗口不存在，此方法将浏览栏窗口创建成一个大小适当的子窗口，它的父窗口就是由SetSite获得的那个窗口。子窗口的句柄存储在m_hwnd变量中。<br>
                        <pre>BOOL CExplorerBar::RegisterAndCreateWindow(void)
                        {
                        //如果这个窗口不存在，则创建它
                        if(!m_hWnd)
                        {
                        //子窗口不能没有父窗口
                        if(!m_hwndParent)
                        {
                        return FALSE;
                        }
                        //如果窗口类没有注册，则必须注册
                        WNDCLASS wc;
                        if(!GetClassInfo(g_hInst, EB_CLASS_NAME, &amp;wc))
                        {
                        ZeroMemory(&amp;wc, sizeof(wc));
                        wc.style          = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
                        wc.lpfnWndProc    = (WNDPROC)WndProc;
                        wc.cbClsExtra     = 0;
                        wc.cbWndExtra     = 0;
                        wc.hInstance      = g_hInst;
                        wc.hIcon          = NULL;
                        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
                        wc.hbrBackground  = (HBRUSH)CreateSolidBrush(RGB(0, 0, 192));
                        wc.lpszMenuName   = NULL;
                        wc.lpszClassName  = EB_CLASS_NAME;
                        if(!RegisterClass(&amp;wc))
                        {
                        //如果注册失败，下面的CreateWindow函数将失败
                        }
                        }
                        RECT  rc;
                        GetClientRect(m_hwndParent, &amp;rc);
                        //创建这个窗口。WndProc 将建立m_hWnd变量
                        CreateWindowEx(   0,
                        EB_CLASS_NAME,
                        NULL,
                        WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER,
                        rc.left,
                        rc.top,
                        rc.right - rc.left,
                        rc.bottom - rc.top,
                        m_hwndParent,
                        NULL,
                        g_hInst,
                        (LPVOID)this);
                        }
                        return (NULL != m_hWnd);
                        }</pre>
                        <em><u><a name=IPersistStream接口>IPersistStream接口</a></u></em><br>IE将调用浏览栏的IPersistStream接口，以便允许这个浏览栏加载或存储持久性数据。如果没有持久性数据，这个方法仍然必须返回一个成功代码。IPersistStream接口从IPersist继承而来，所以要实现五个方法：<br>GetClassID, IsDirty, Load, Save, GetSizeMax。<br>本文的这个浏览栏例子不使用持久性数据，并且只有IPersistStream的最小实现。GetClassID返回对象的CLSID（CLSID_SampleExplorerBar），其余的方法返回S_OK, 或者S_FALSE, 或者 E_NOTIMPL。有关细节请参见IPersistStream的实现。<br><br><em><u><a name=IDeskBand接口>IDeskBand接口</a></u></em><br>IDeskBand接口是区对象专用接口。它只有一个方法。IDeskBand接口从IDockingWindow继承而来，而IDockingWindow又从IOleWindow继承而来。<br>IOleWindow有两个方法：GetWindow 和 ContextSensitiveHelp。浏览栏例子的GetWindow实现返回浏览栏的子窗口句柄m_hwnd。因为不实现上下文敏感帮助，所以ContextSensitiveHelp返回E_NOTIMPL。<br>IDockingWindow接口有三个方法：ShowDW, CloseDW, 和 ResizeBorder。ResizeBorder不在任何区对象中使用，应该返回E_NOTIMPL。ShowDW方法根据其不同的参数值控制浏览栏窗口的显示或隐藏：<br>
                        <pre>STDMETHODIMP CExplorerBar::ShowDW(BOOL fShow)
                        {
                        if(m_hWnd)
                        {
                        if(fShow)
                        {
                        //显示窗口
                        ShowWindow(m_hWnd, SW_SHOW);
                        }
                        else
                        {
                        //隐藏窗口
                        ShowWindow(m_hWnd, SW_HIDE);
                        }
                        }
                        return S_OK;
                        }
                        CloseDW方法摧毁浏览栏窗口：
                        STDMETHODIMP CExplorerBar::CloseDW(DWORD dwReserved)
                        {
                        ShowDW(FALSE);
                        if(IsWindow(m_hWnd))
                        DestroyWindow(m_hWnd);
                        m_hWnd = NULL;
                        return S_OK;
                        }</pre>
                        其余的方法，如GetBandInfo是IDeskBand专用的。IE使用它来指定浏览栏的标示符以及视图模式。IE还可能填写DESKBANDINFO结构的dwMask成员从浏览栏请求更多的信息，这个结构用第三个参数传递。GetBandInfo应该存储这个标示符和视图模式并用所请求的数据填写DESKBANDINFO结构。下面是本文浏览栏例子所实现GetBandInfo：<br>
                        <pre>STDMETHODIMP CExplorerBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi)
                        {
                        if(pdbi)
                        {
                        m_dwBandID = dwBandID;
                        m_dwViewMode = dwViewMode;
                        if(pdbi-&gt;dwMask &amp; DBIM_MINSIZE)
                        {
                        pdbi-&gt;ptMinSize.x = MIN_SIZE_X;
                        pdbi-&gt;ptMinSize.y = MIN_SIZE_Y;
                        }
                        if(pdbi-&gt;dwMask &amp; DBIM_MAXSIZE)
                        {
                        pdbi-&gt;ptMaxSize.x = -1;
                        pdbi-&gt;ptMaxSize.y = -1;
                        }
                        if(pdbi-&gt;dwMask &amp; DBIM_INTEGRAL)
                        {
                        pdbi-&gt;ptIntegral.x = 1;
                        pdbi-&gt;ptIntegral.y = 1;
                        }
                        if(pdbi-&gt;dwMask &amp; DBIM_ACTUAL)
                        {
                        pdbi-&gt;ptActual.x = 0;
                        pdbi-&gt;ptActual.y = 0;
                        }
                        if(pdbi-&gt;dwMask &amp; DBIM_TITLE)
                        {
                        lstrcpyW(pdbi-&gt;wszTitle, L"浏览栏例子");
                        }
                        if(pdbi-&gt;dwMask &amp; DBIM_MODEFLAGS)
                        {
                        pdbi-&gt;dwModeFlags = DBIMF_VARIABLEHEIGHT;
                        }
                        if(pdbi-&gt;dwMask &amp; DBIM_BKCOLOR)
                        {
                        //通过移开这个标志来使用默认的背景颜色
                        pdbi-&gt;dwMask &amp;= ~DBIM_BKCOLOR;
                        }
                        return S_OK;
                        }
                        return E_INVALIDARG;
                        }</pre>
                        <strong><em><a name=可选择的接口实现>可选择的接口实现</a></em></strong><br>由两个接口的实现是可选择的，一个是IInputObject，另一个是 IContextMenu。本文的浏览栏例子实现了IInputObject。对于IContextMenu的实现细节请参考有关文档。<br><br><em><u><a name=IInputObject接口>IInputObject接口</a></u></em><br>如果某个band对象要接受用户输入。那就必须实现IInputObject接口。IE实现IInputObjectSite并用IInputObject维护用户的输入焦点。浏览栏需要实现三个方法：UIActivateIO, HasFocusIO, 和 TranslateAcceleratorIO。<br>IE调用UIActivateIO通知浏览栏它以被激活或者被置灰。当被激活时，浏览栏例子调用SetFocus来设置窗口输入焦点。<br>当要确定哪个窗口有输入焦点时，IE调用HasFocusIO。如果浏览栏的窗口或它的子窗口之一有输入焦点，HasFocusIO返回S_OK。否则，它返回S_FALSE。<br>TranslateAcceleratorIO允许对象处理键盘加速键。本文浏览栏例子没有实现这个方法，所以它返回S_FALSE。<br>浏览栏例子实现IInputObjectSite的细节如下：<br>
                        <pre>STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg)
                        {
                        if(fActivate)
                        SetFocus(m_hWnd);
                        return S_OK;
                        }
                        STDMETHODIMP CExplorerBar::HasFocusIO(void)
                        {
                        if(m_bFocus)
                        return S_OK;
                        return S_FALSE;
                        }
                        STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg)
                        {
                        return S_FALSE;
                        }</pre>
                        <strong><em><a name=窗口过程>窗口过程</a></em></strong><br>因为区对象的显示用的是子窗口，所以它必须实现窗口过程来处理Windows消息。浏览栏例子实现了一个最简单的版本，它的窗口过程只处理了五个消息：WM_NCCREATE, WM_PAINT, WM_COMMAND, WM_SETFOCUS, 和 WM_KILLFOCUS。如果要实现更多的功能，很容易扩充使它处理其它的消息。<br>
                        <pre>LRESULT CALLBACK CExplorerBar::WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
                        {
                        CExplorerBar  *pThis = (CExplorerBar*)GetWindowLong(hWnd, GWL_USERDATA);
                        switch (uMessage)
                        {
                        case WM_NCCREATE:
                        {
                        LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
                        pThis = (CExplorerBar*)(lpcs-&gt;lpCreateParams);
                        SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);
                        //设置窗口句柄
                        pThis-&gt;m_hWnd = hWnd;
                        }
                        break;
                        case WM_PAINT:
                        return pThis-&gt;OnPaint();
                        case WM_COMMAND:
                        return pThis-&gt;OnCommand(wParam, lParam);
                        case WM_SETFOCUS:
                        return pThis-&gt;OnSetFocus();
                        case WM_KILLFOCUS:
                        return pThis-&gt;OnKillFocus();
                        }
                        return DefWindowProc(hWnd, uMessage, wParam, lParam);
                        }</pre>
                        这里WM_COMMAND消息处理器简单地返回零。WM_PAINT消息处理器创建文本并显示在资源管理器或IE的区对象中。<br>
                        <pre>LRESULT CExplorerBar::OnPaint(void)
                        {
                        PAINTSTRUCT ps;
                        RECT        rc;
                        BeginPaint(m_hWnd, &amp;ps);
                        GetClientRect(m_hWnd, &amp;rc);
                        SetTextColor(ps.hdc, RGB(255, 255, 255));
                        SetBkMode(ps.hdc, TRANSPARENT);
                        DrawText(ps.hdc, TEXT("浏览栏例子"), -1, &amp;rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
                        EndPaint(m_hWnd, &amp;ps);
                        return 0;
                        }</pre>
                        WM_SETFOCUS 和 WM_KILLFOCUS消息处理器通过调用本现场的IInputObjectSite::OnFocusChangeIS方法通知输入焦点现场改变：<br>
                        <pre>LRESULT CExplorerBar::OnSetFocus(void)
                        {
                        FocusChange(TRUE);
                        return 0;
                        }
                        LRESULT CExplorerBar::OnKillFocus(void)
                        {
                        FocusChange(FALSE);
                        return 0;
                        }
                        void CExplorerBar::FocusChange(BOOL bFocus)
                        {
                        m_bFocus = bFocus;
                        //通知焦点已改变的输入对象现场
                        if(m_pSite)
                        {
                        m_pSite-&gt;OnFocusChangeIS((IDockingWindow*)this, bFocus);
                        }
                        }</pre>
                        <strong><a name=四、总结>四、总结</a></strong><br><br>区对象提供了灵活和强大的扩展方式，通过定制浏览栏使得IE的功能大为增强。桌面区的实现扩展了普通窗口的能力。尽管需要一些对COM的编程，但终究以子窗口的形式提供了一种用户界面。从而使今后的许多这种编程实现都能用类似的Windows编程技术。虽然本文所讨论的例子只提供了有限的功能，但它示范了区对象全部的特性，并且可以在此基础上进行扩充来创建独特和功能强大的的用户界面。 </td>
                    </tr>
                </tbody>
            </table>
            <p><br><br><br></p>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/mydriverc/aggbug/29022.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mydriverc/" target="_blank">旅途</a> 2007-07-30 16:20 <a href="http://www.cppblog.com/mydriverc/articles/29022.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏 </title><link>http://www.cppblog.com/mydriverc/articles/29017.html</link><dc:creator>旅途</dc:creator><author>旅途</author><pubDate>Mon, 30 Jul 2007 08:00:00 GMT</pubDate><guid>http://www.cppblog.com/mydriverc/articles/29017.html</guid><wfw:comment>http://www.cppblog.com/mydriverc/comments/29017.html</wfw:comment><comments>http://www.cppblog.com/mydriverc/articles/29017.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mydriverc/comments/commentRss/29017.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mydriverc/services/trackbacks/29017.html</trackback:ping><description><![CDATA[<p align=center><strong>ATL 实现定制的 IE 浏览器栏、工具栏和桌面工具栏<br></strong><br>作者：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#121;&#97;&#110;&#103;&#102;&#101;&#110;&#103;&#64;&#102;&#116;&#114;&#46;&#99;&#111;&#109;&#46;&#99;&#110;"><font color=#0000ff><u>杨老师</u></font></a></p>
<p><a href="http://www.vckbase.com/code/downcode.asp?id=2675"><u><font color=#0000ff>下载源代码</font></u></a><br><br><strong>关键字</strong>：Band，Desk Band，Explorer Band，Tool Band，浏览器栏，工具栏，桌面工具栏<br><br><strong>一、引言</strong><br>　　最近，由于工作的要求，我需要在 IE 上做一些开发工作。于是在 MSDN 上翻阅了一些资料，根据 MSDN 上的说明我用 ATL 胜利完成了&#8220;资本家老板&#8221;分配的任务。<br>（并且在白天睡觉的过程中梦到了老板给我加工资啦......）<br>现在，我把 MSDN 上的原文资料，经过翻译整理并把一个 ATL 的实现奉贤给 VCKBASE 上的朋友们。</p>
<ul>
    <li><a href="http://www.vckbase.com/document/viewdoc/?id=1457#二、概念"><u><font color=#0000ff>概念</font></u></a>
    <li><a href="http://www.vckbase.com/document/viewdoc/?id=1457#三、原理"><u><font color=#0000ff>原理</font></u></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.1　基本_band_对象"><u><font color=#0000ff>基本band 对象</font></u></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.2　必须实现的_COM_接口"><u><font color=#0000ff>必须实现的 COM 接口</font></u></a><br>&nbsp;&nbsp;&nbsp; <a href="http://www.vckbase.com/document/viewdoc/?id=1457#IPersistStream"><u><font color=#0000ff>IPersistStream</font></u></a><br>&nbsp;&nbsp;&nbsp; <a href="http://www.vckbase.com/document/viewdoc/?id=1457#IObjectWithSite"><u><font color=#0000ff>IObjectWithSite</font></u></a><br>&nbsp;&nbsp;&nbsp; <a href="http://www.vckbase.com/document/viewdoc/?id=1457#IDeskBand"><font color=#0000ff><u>IDeskBand、IDockingWindow、IOleWindow</u></font></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.3　选择实现的_COM_接口"><font color=#0000ff><u>选择实现的 COM 接口</u></font></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=1457#3.4　Band_对象注册"><u><font color=#0000ff>Band 对象注册</font></u></a>
    <li><a href="http://www.vckbase.com/document/viewdoc/?id=1457#四、_ATL_实现"><u><font color=#0000ff>ATL 实现</font></u></a> </li>
</ul>
<p><strong><a name=二、概念>二、概念</a></strong><br>　　在翻译的过程中，有两个词汇非常不好理解。第一个词是 Band 对象，词典中翻译为&#8220;镶边、裙子边、带子、乐队......&#8221;我的英文水平有限，实在不知道应该翻译为什么词汇更合适。于是我毅然决然地决定：在如下的论述中，依然使用 band 这个词！（什么？没听明白？我的意思就是说，我不翻译这个词了）但到底 Band 对象应该如何理解那？请看图一：<br><br><img height=399 alt="" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands1.jpg" width=613 border=0><br>图一<br><br>　　图一中画红圈的地方，分别称作&#8220;垂直的浏览器栏&#8221;、&#8220;水平的浏览器栏&#8221;、&#8220;工具栏&#8221;和&#8220;桌面工具栏&#8221;。这些&#8220;栏&#8221;，都可以在 IE 的&#8220;查看&#8221;菜单中或鼠标右键的上下文快捷方式菜单中显示或隐藏起来。这些界面窗口的实现，其实就是实现一种 COM 接口对象，而这个对象叫 band。这个概念实在是只能意会而无法言传的，我总不能在文章中把它翻译为&#8220;总是靠在 IE 主窗口边上的对象&#8221;吧？^_^<br>　　另外，还有一个词叫 site。这个很好翻译，叫&#8220;站点&#8221;！。呵呵，我敢打包票，如果你要能理解这个翻译在计算机类文章中的含义，那就只能恭喜你了，你的智慧太高了。（都是学计算机软件的人，做人的差距咋就这么大呢？）在本篇文章中，site 可以这样理解：IE 的主框架四周，就好比是&#8220;汽车站&#8221;，那些 band 对象，就好比是&#8220;汽车&#8221;。band 汽车总是可以停靠在&#8220;汽车站&#8221;上。所以，site 就是&#8220;站点&#8221;，它也是 COM 接口的对象（IObjectWithSite、IInputObjectSite）。<br><br><strong><a name=三、原理>三、原理</a></strong><br><br><em><a name=3.1　基本_band_对象>3.1　基本 band 对象</a></em><br>　　Band 对象，从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一个 COM 对象，必须放在一个容器中去使用，当然使用它们就好象使用普通窗口是一样的。IE 就是一个容器，桌面 Shell 也是一个容器，它们提供不同的函数功能，但基本的实现是相似的。<br>　　Band 对象分三种类型，浏览器栏 band（Explorer bands）、工具栏 band（Tool Bands）和桌面工具栏(Desk bands)，而浏览器栏 band 又有两种表现形式：垂直和水平的。那么 IE 和 Shell 如何区分并加载这些 bands 对象呢？方法是：你要对不同的 band 对象，在注册表中注册不同的组件类型（CATID）。<br><br>
<table cellSpacing=1 width="83%" border=1>
    <tbody>
        <tr>
            <td width="22%">
            <p align=center><strong><span lang=en-us>Band </span>样式</strong></p>
            </td>
            <td width="25%">
            <p align=center><strong>组件类型</strong></p>
            </td>
            <td width="51%">
            <p align=center><strong>CATID</strong></p>
            </td>
        </tr>
        <tr>
            <td align=middle width="22%">垂直的浏览器栏</td>
            <td align=middle width="25%"><font face=宋体 size=3><span lang=en-us>CATID_InfoBand</span></font></td>
            <td align=middle width="51%">00021493-0000-0000-C000-000000000046</td>
        </tr>
        <tr>
            <td align=middle width="22%">水平的浏览器栏</td>
            <td align=middle width="25%"><font face=宋体 size=3><span lang=en-us>CATID_CommBand</span></font></td>
            <td align=middle width="51%">00021494-0000-0000-C000-000000000046</td>
        </tr>
        <tr>
            <td align=middle width="22%">桌面的工具栏</td>
            <td align=middle width="25%"><font face=宋体 size=3><span lang=en-us>CATID_DeskBand</span></font></td>
            <td align=middle width="51%">00021492-0000-0000-C000-000000000046</td>
        </tr>
    </tbody>
</table>
<br>　　IE 工具栏不使用组件类型注册，而是使用在注册进行 CLSID 的登记方式。详细情况见 3.3。<br>　　在例子程序中，实现了全部四个类型的 band 对象，垂直浏览器栏(CVerticalBar)显示了一个 HTML 文件，并且实现了对 IE 主窗口浏览网页的导航等功能；水平的浏览器栏(CHorizontalBar)是一个编辑窗，它同步显示当前网页的 BODY 源文件内容；IE 工具栏(CToolBar)最简单，只是添加了一个空的工具栏；桌面工具栏(CDeskBar)实现了一个单行编辑窗口，你可以在上面输入命令行或文件名称，回车后它会执行 Shell 的打开动作。<br><br><em><a name=3.2　必须实现的_COM_接口>3.2　必须实现的 COM 接口</a></em><br>　　Band 对象是 IE 或 Shell 的进程内服务器，所以它被包装在 DLL 中。而作为 COM 对象，它必须要实现 IUnknown 和 IClassFactory 接口。（大家可以不同操心，因为我们用 ATL 写程序，这两个接口是不用我们自己写代码的。）另外，Band 对象还必须实现 IDeskBand、IObjectWithSite 和 IPersistStream 三个接口：<br>　　<a name=IPersistStream>IPersistStream</a> 是持续性接口的一种。当 IE 加载 band 对象的时候，它通过这个接口的 Load 方法传递属性值给对象，让其进行初始化；而当卸载前，IE 则调用这个接口的 Save 方法保存对象的属性。用 ATL 实现这个接口很简单： </p>
<pre>class ATL_NO_VTABLE Cxxx : 	......	public IPersistStreamInitImpl, // 添加继承	......{public:	BOOL m_bRequiresSave; // IPersistStreamInitImpl 所必须的变量......BEGIN_COM_MAP(CVerticalBar)	......	COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)	COM_INTERFACE_ENTRY2(IPersistStream, IPersistStreamInit)	COM_INTERFACE_ENTRY(IPersistStreamInit)	......END_COM_MAP()BEGIN_PROP_MAP(Cxxx)...... // 添加需要持续性的属性END_PROP_MAP()		</pre>
　　上面的代码，其实实现的是 IPersistStreamInit 接口，不过没有关系，因为 IPersistStreamInit 派生自 IPersistStream，实例化了派生类，自然就实例化了基类。在例子程序中，我只在桌面工具栏对象中添加了持续性属性，用来保存和初始化&#8220;命令行&#8221;。另外 COM_INTERFACE_ENTRY2(A，B)表示的含义是：如果想查询A接口的指针，则提供B接口指针来代替。为什么可以这样那？因为B接口派生自A接口，那么B接口的前几个函数必然就是A接口的函数了，自然B接口的地址其实和A接口的地址是一样的了。<br>　　<a name=IObjectWithSite>IObjectWithSite</a> 是 IE 用来对插件进行管理和通讯用的一个接口。必须要实现这个接口的2个函数：SetSite() 和 GetSite()。当 IE 加载 band 对象和释放 band 对象的时候，都要调用 SetSite()函数，那么在这个函数里正好是写初始化和释放操作代码的地方：
<pre>STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite){	if( NULL == pUnkSite )	// 释放 band 的时候	{		// 如果加载的时候，保存了一些接口		// 那么现在：释放它	}	else	// 加载 band 的时候	{		m_hwndParent = NULL;	// 装载 band 的父窗口(就是带有标题的那个框架窗口)		// 这个窗口的句柄，是调用 IUnknown::QueryInterface() 得到 IOleWindow		// 然后调用 IOleWindow::GetWindow() 而获得的。		CComQIPtr&lt; IOleWindow, &amp;IID_IOleWindow &gt; spOleWindow(pUnkSite);		if( spOleWindow )	spOleWindow-&gt;GetWindow(&amp;m_hwndParent);		if( !m_hwndParent )	return E_FAIL;				// 现在，正好是建立子窗口的时机。		// 注意，子窗口建立的时候，不要使用 WS_VISIBLE 属性		... ...		// 在例子程序中，用 CAxWindow 实现了一个能包容ActiveX的容器窗口(垂直浏览器栏)		// 在例子程序中，用 WIN API 函数 CreateWindow 实现了标准窗口(水平浏览器栏、工具栏)		// 在例子程序中，用 CWindowImpl 实现了一个包容窗口(桌面工具栏)		/*********************************************************/		   以下部分，根据 band 对象特有的功能，是可以选择实现的		**********************************************************/				// 如果子窗口实现了用户输入，那么必须实现 IInputObject 接口，		// 而该接口是被 IE 的 IInputObjectSite 调用的，因此在你的对象		// 中，应该保存 IInputObjectSite 的接口指针。		// 在类的头文件中，定义：		// CComQIPtr&lt; IInputObjectSite, &amp;IID_IInputObjectSite &gt; m_spSite;		m_spSite = pUnkSite;	// 保存 IInputObjectSite 指针		if( !m_spSite )		return E_FAIL;		// 你需要控制 IE 的主框架吗？		// 那么在类的头文件中，定义：		// CComQIPtr&lt; IWebBrowser2, &amp;IID_IWebBrowser2 &gt; m_spFrameWB;		// 然后，先取得 IServiceProvider,再取得 IWebBrowser2		CComQIPtr &lt; IServiceProvider, &amp;IID_IServiceProvider&gt; spSP(pUnkSite);		if( !spSP )	return E_FAIL;		spSP-&gt;QueryService( SID_SWebBrowserApp, &amp;m_spFrameWB );		if( !m_spFrameWB)	return E_FAIL;		// 如果你取得了 IE 主框架的 IWebBrowser2 指针		// 那么，当它发生了什么事情，你难道不想知道吗？		// 定义：CComPtr m_spCP;		CComQIPtr&lt; IConnectionPointContainer,			&amp;IID_IConnectionPointContainer&gt; spCPC( m_spFrameWB );		if( spCPC )		{			spCPC-&gt;FindConnectionPoint( DIID_DWebBrowserEvents2, &amp;m_spCP );			if( m_spCP )			{				m_spCP-&gt;Advise( reinterpret_cast&lt; IDispatch * &gt;( this ), &amp;m_dwCookie );			}		}		// 咳~~~ 不说了，看源码去吧。这里能干的事情太多了... ...	}	return S_OK;}		</pre>
<a name=IDeskBand>IDeskBand</a> 是一个特殊的 band 对象接口，有一个方法函数：GetBarInfo()；<br>IDockingWindow 是 IDeskBank 的基类，有3个方法函数：ShowDW()、CloseDW()、ResizeBorderDW()；<br>IOleWindow 又是 IDockingWindow 的基类，有2个方法函数：GetWindow()、ContextSensitiveHelp()； <br><br>　　首先声明 IDeskBand ,然后要实现 IDeskBand 接口的共6个函数，这些函数比较简单，不同类型的 band 对象，其实现方法也都基本一致：
<pre>class ATL_NO_VTABLE Cxxx : 	......	public IDeskBand,	......{......BEGIN_COM_MAP(Cxxx)	......	COM_INTERFACE_ENTRY_IID(IID_IDeskBand, IDeskBand)	......END_COM_MAP()// IOleWindowSTDMETHODIMP Cxxx::GetWindow(HWND * phwnd){	// 取得 band 对象的窗口句柄	// m_hWnd 是建立窗口时候保存的	*phwnd = m_hWnd;		return S_OK;}STDMETHODIMP Cxxx::ContextSensitiveHelp(BOOL fEnterMode){	// 上下文帮助，参考 IContextMenu 接口	return E_NOTIMPL;}// IDockingWindowSTDMETHODIMP CVerticalBar::ShowDW(BOOL bShow){	// 显示或隐藏 band 窗口	if( m_hWnd )		::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE);	return S_OK;}STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved){	// 销毁 band 窗口	if( ::IsWindow( m_hWnd ) )		::DestroyWindow( m_hWnd );	m_hWnd = NULL;    return S_OK;}STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved){	// 当框架窗口的边框大小改变时	return E_NOTIMPL;}// IDeskBandSTDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode,  DESKBANDINFO* pdbi){	         // 取得 band 的基本信息，你需要填写 pdbi 参数作为返回	if( NULL == pdbi )		return E_INVALIDARG;	// 如果将来需要调用 IOleCommandTarget::Exec() 则需要保存这2个参数	m_dwBandID = dwBandID;	m_dwViewMode = dwViewMode;	if(pdbi-&gt;dwMask &amp; DBIM_MINSIZE)	{	// 最小尺寸		pdbi-&gt;ptMinSize.x = 10;		pdbi-&gt;ptMinSize.y = 10;	}	if(pdbi-&gt;dwMask &amp; DBIM_MAXSIZE)	{	// 最大尺寸 (-1 表示 4G)		pdbi-&gt;ptMaxSize.x = -1;		pdbi-&gt;ptMaxSize.y = -1;	}	if(pdbi-&gt;dwMask &amp; DBIM_INTEGRAL)	{		pdbi-&gt;ptIntegral.x = 1;		pdbi-&gt;ptIntegral.y = 1;	}	if(pdbi-&gt;dwMask &amp; DBIM_ACTUAL)	{		pdbi-&gt;ptActual.x = 0;		pdbi-&gt;ptActual.y = 0;	}	if(pdbi-&gt;dwMask &amp; DBIM_TITLE)	{	// 窗口标题		wcscpy(pdbi-&gt;wszTitle,L"窗口标题");	}	if(pdbi-&gt;dwMask &amp; DBIM_MODEFLAGS)	{		pdbi-&gt;dwModeFlags = DBIMF_VARIABLEHEIGHT;	}	if(pdbi-&gt;dwMask &amp; DBIM_BKCOLOR)	{	// 如果使用默认的背景色，则移除该标志		pdbi-&gt;dwMask &amp;= ~DBIM_BKCOLOR;	}	return S_OK;}		</pre>
<em><a name=3.3　选择实现的_COM_接口>3.3　选择实现的 COM 接口</a></em><br>　　有两个接口不是必须实现的，但也许很有用：IInputObject 和 IContextMenu。如果 band 对象需要接收用户的输入，那么必须实现 IInputObject 接口。IE 实现了 IInputObjectSite 接口，当容器中有多个输入窗口时，它调用 IInputObject 接口方法去负责管理用户的输入焦点。<br>在浏览器栏中需要实现3个函数：UIActivateIO()、HasFocusIO()、TranslateAcceleratorIO()。<br>当浏览器栏激活或失去活性的时候，IE 调用 UIActivateIO 函数，当激活的时候，浏览器栏一般调用 SetFocus 去设置它自己窗口的焦点。当 IE 需要判断哪个窗口有焦点的时候，它调用 HasFocusIO 。当浏览器栏的窗口或其子窗口有输入焦点时，则应返回 S_OK，否则返回 S_FALSE。TranslateAcceleratorIO 允许对象处理加速键，例子程序中没有实现，所以直接返回 S_FALSE。
<pre>STDMETHODIMP CExplorerBar::UIActivateIO(BOOL fActivate, LPMSG pMsg){    if(fActivate)        SetFocus(m_hWnd);    return S_OK;}STDMETHODIMP CExplorerBar::HasFocusIO(void){    if(m_bFocus)        return S_OK;    return S_FALSE;}STDMETHODIMP CExplorerBar::TranslateAcceleratorIO(LPMSG pMsg){    return S_FALSE;}      </pre>
　　Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命令。而 IOleCommandTarget 接口指针，则可以通过调用包容器的 IInputOjbectSite::QueryInterface（IID_IOleCommandTarget,...） 函数得到。CGID_DeskBand 是命令组，当一个 band 对象的 GetBandInfo 被调用的时候，包容器通过 dwBandID 参数指定一个 ID 给 band 对象，对象要保存住这个ID，以便调用 IOleCommandTarget::Exec()的时候使用。ID 的命令有：
<ul>
    <li>DBID_BANDINFOCHANGED<br>Band 的信息变化。设置参数 pvaIn 为 band ID， 该 ID 就是最近一次调用 GetBandInfo 所得到的值，容器会调用 band 对象的 GetBandInfo 函数来更新请求信息。
    <li>DBID_MAXIMIZEBAND <br>最大化 band。设置参数 pvaIn 为 band ID，该 ID 就是最近一次调用 ?GetBandInfo ?所得到的值。
    <li>DBID_SHOWONLY <br>打开或关闭容器中其它的 bands。 设置参数 pvaIn 为VT_UNKNOWN 类型，它可以是如下的值：<br>　
    <table class=clsStd width=727 border=1>
        <tbody>
            <tr>
                <th width=62><font face=宋体 size=3>值</font></th>
                <th width=650><font face=宋体 size=3>描述</font></th>
            </tr>
            <tr>
                <td align=middle width=62><font face=宋体 size=3>pUnk</font></td>
                <td width=650><font face=宋体 size=3><span lang=en-us>band </span>对象的 <strong>IUnknown</strong> 指针，其它的桌面<span lang=en-us> bands </span>将被隐藏</font></td>
            </tr>
            <tr>
                <td align=middle width=62><font face=宋体 size=3>0</font></td>
                <td width=650><font face=宋体 size=3>隐藏所有的桌面<span lang=en-us> bands</span></font></td>
            </tr>
            <tr>
                <td align=middle width=62><font face=宋体 size=3>1</font></td>
                <td width=650><font face=宋体 size=3>显示所有的桌面 <span lang=en-us>bands</span></font></td>
            </tr>
        </tbody>
    </table>
    <br>
    <li>DBID_PUSHCHEVRON<br>在菜单项左边显示&#8220;v&#8221;的选择标志。容器发送一个 RB_PUSHCHEVRON 消息，当 band 对象接收到通知消息 RBN_CHEVRONPUSHED 提示它显示一个"v"的标志。设置 IOleCommandTarget::Exec 函数中 nCmdExecOpt 参数为 band ID，该 ID 是最近一次调用 GetBandInfo ?所得到的值，设置 IOleCommandTarget::Exec 函数中 pvaIn 参数为 VT_I4 类型，这是应用程序定义的一个值，它通过通知消息 RBN_CHEVRONPUSHED 中lAppValue 回传给 band 对象。 </li>
</ul>
<p><em><a name=3.4　Band_对象注册>3.4　Band 对象注册</a></em><br>　　Band 对象必须注册为一个 OLE 进程内的服务器，并且支持 apartment 线程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏，它加到 IE 菜单的&#8220;查看\浏览器栏&#8221;中；对于工具栏 band ，它加到 IE 菜单的&#8220;查看\工具栏&#8221;中；对于桌面 band， 它加到系统任务栏的快捷菜单中。在菜单资源中，可以使用&#8220;&amp;&#8221;指明加速键。<br><br>通常，一个基本的 band 对象的注册表项目是：<br><br><strong>HKEY_CLASSES_ROOT <br>CLSID <br><em>{你的 band 对象的 CLSID}</em></strong> <br>　　(Default) = 菜单的文字 <br>　　InProcServer32 <br>　　　(Default) = DLL 的全路径文件名 <br>　　　ThreadingModel= Apartment<br><br>工具栏 bands 还必须把它们的 CLSID 注册到 IE 的注册表中。<br><br>在 <strong>HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar</strong> 下给出 CLSID 作为键名，而其键值是被忽略的。<br><br><strong>HKEY_LOCAL_MACHINE <br>Software <br>Microsoft <br>Internet Explorer <br>Toolbar </strong><br>　　{你的 band 对象的 CLSID}<br><br>　　还有几个可选的注册表项目(例子程序并不是这样实现的)。比如，你想让浏览器栏显示 HTML 的话，必须要如下设置注册表： <br><br><strong>HKEY_CLASSES_ROOT <br>CLSID <br><em>{你的 Band 对象的 CLSID} </em><br>Instance <br>CLSID <br>　　</strong>(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}<br><br>同时，如果要指定一个本地的 HTML 文件，那么要如下设置： <br><br><strong>HKEY_CLASSES_ROOT <br>CLSID <br><em>{你的 Band 对象的 CLSID}</em> <br>Instance <br>InitPropertyBag <br>　　</strong>Url<br><br>　　另外，还可以指定浏览器栏的宽和高，当然，它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓，因为当用户调整了浏览器栏的大小后，会自动保存在注册表中的。<br><br><strong>HKEY_CURRENT_USER <br>Software <br>Microsoft <br>Internet Explorer <br>Explorer Bars <br>{你的 Band 对象的 CLSID} <br>　　</strong>BarSize<br><br>　　BarSize 键的类型必须是 REG_BINARY 类型，它有8个字节。左起前4个字节，是用16进制表示的像素宽度或高度，后4个字节保留，你应该设置为0。下面是一个可以在浏览器栏上显示 HTML 文件的全部注册表项目的例子，默认宽度为291（0x123）个像素点： <br><br><strong>HKEY_CLASSES_ROOT <br>CLSID <br><em>{你的 Band 对象的 CLSID} </em></strong><br>　(Default) = 菜单文字 <br>　<strong>InProcServer32 </strong><br>　　(Default) = DLL 的全路径文件名 <br>　　ThreadingModel= Apartment<br><strong>Instance <br>CLSID </strong><br>　　(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}<br><strong>InitPropertyBag</strong> <br>　　Url= 你的 HTML 文件名<br><br><strong>HKEY_CURRENT_USER <br>Software <br>Microsoft <br>Internet Explorer <br>Explorer Bars <br>{你的 Band 对象的 CLSID} </strong><br>　　BarSize= 23 01 00 00 00 00 00 00<br><br>　　对于注册表的设置，用 ATL 实现其实是异常简单的。打开工程的 xxx.rgs 文件，并手工编辑一下就可以了。 下面这个文件源码，是例子程序中 IE 工具栏的注册表样式，HKLM 是需要手工添加的，因为它不使用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加： </p>
<pre>BEGIN_CATEGORY_MAP(Cxxx)			// 向注册表中注册 COM 类型	IMPLEMENTED_CATEGORY(CATID_InfoBand)	// 垂直样式的浏览器栏END_CATEGORY_MAP()		</pre>
IE 工具栏类型 band 对象的&#8220;.rgs&#8221;文件
<pre>HKCR	// 这个项目是 ATL 帮你生成的，你只要手工修改&#8220;菜单上的文字&#8221;就可以了{	Bands.ToolBar.1 = s ''ToolBar Class''	{		CLSID = s ''{ 你的 CLSID }''	}	Bands.ToolBar = s ''ToolBar Class''	{		CLSID = s ''{ 你的 CLSID }''		CurVer = s ''Bands.ToolBar.1''	}	NoRemove CLSID	{		ForceRemove { 你的 CLSID } = s ''用在菜单上的文字(&amp;T)''		{			ProgID = s ''Bands.ToolBar.1''			VersionIndependentProgID = s ''Bands.ToolBar''			ForceRemove ''Programmable''			InprocServer32 = s ''%MODULE%''			{				val ThreadingModel = s ''Apartment''			}			''TypeLib'' = s ''{xxxx-xxxx-xxxxxxxxxxxxxxx}''		}	}}HKLM	// 这个项目是手工添加的IE工具栏所特有的{	Software	{		Microsoft		{			''Internet Explorer''			{				NoRemove Toolbar				{					ForceRemove val { 你的 CLSID } = s ''随便给个说明性文字串''				}			}		}	}}		</pre>
<strong><a name=四、_ATL_实现>四、 ATL 实现</a></strong><br>　　下载代码后(VC 6.0 工程)，请参照前面的说明仔细阅读，代码中也有一些关键点的注释。如果想运行，则可以用 regsvr32.exe 进行注册，然后打开 IE 浏览器或资源浏览器就可以看到效果了。如果想自己实践一下，可以按照如下的步骤构造工程：<br><br>4.1　建立一个 ATL DLL 工程<br>4.2　添加 New ATL Object...，选择 Internet Explorer Object，选这个类型的目的是让向导给我们添加 IObjectWithSite 的支持。如果你使用的是 .net 环境，则不要忘记选择支持这个接口。<br><br><img height=257 alt="" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands2.jpg" width=413 border=0><br><br>4.3　输入对象名称，比如我想建立一个垂直的浏览器栏，不妨叫它 VerBar<br><br><img height=260 alt="" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands3.jpg" width=421 border=0><br><br>4.4　线程模型必须选择 Apartment，接口类型的选择无所谓，看你想不想支持 IDispatch 接口功能了。在例子程序中的垂直浏览器栏中，由于想更简单的操纵 IE 和从 IE 中接受事件（连接点），选择 Dual 是必要的。聚合选项，你只要别选择 Only 就可以了。<br><br><img height=260 alt="" src="http://www.vckbase.com/document/journal/vckbase42/images/yfbands4.jpg" width=421 border=0><br><br>4.5　展现你无穷的智慧，开始输入程序吧。如果是 Debug 方式编译，可能会出现一个连接错误，报告找不到_AtlAxCreateControl，那么你要在菜单 Project\Settings...\Link 中增加对 Atl.lib 的连接。或者使用 #pragma comment ( lib, "atl" )加入连接库。<br>4.6　如果想调试代码，在菜单 Project\Settings...\Debug 中输入 IE 的路径名称，比如：&#8220;C:\Program Files\Internet Explorer\IEXPLORE.EXE&#8221;，然后就可以跟踪断点调试了。 编译和调试桌面工具栏的 band 对象，是非常麻烦的，因为计算机启动时自动运行 Shell，而 Shell 就会加载活动的桌面对象。<br><br><strong>五、结束语</strong><br>好了，到这里，就到这里了。祝大家学习快乐^_^&nbsp;<br>
<img src ="http://www.cppblog.com/mydriverc/aggbug/29017.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mydriverc/" target="_blank">旅途</a> 2007-07-30 16:00 <a href="http://www.cppblog.com/mydriverc/articles/29017.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>COM 组件设计与应用（十七） ---持续性</title><link>http://www.cppblog.com/mydriverc/articles/29016.html</link><dc:creator>旅途</dc:creator><author>旅途</author><pubDate>Mon, 30 Jul 2007 07:59:00 GMT</pubDate><guid>http://www.cppblog.com/mydriverc/articles/29016.html</guid><wfw:comment>http://www.cppblog.com/mydriverc/comments/29016.html</wfw:comment><comments>http://www.cppblog.com/mydriverc/articles/29016.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mydriverc/comments/commentRss/29016.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mydriverc/services/trackbacks/29016.html</trackback:ping><description><![CDATA[作者：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#103;&#111;&#111;&#100;&#95;&#121;&#102;&#64;&#115;&#105;&#110;&#97;&#46;&#99;&#111;&#109;"><u><font color=#0000ff>杨老师</font></u></a>
<p><a href="http://www.vckbase.com/code/downcode.asp?id=2784"><u><font color=#0000ff>下载源代码</font></u></a><strong><br><br>一、前言</strong><br>　　我们写程序，经常需要实现这样的需求：<br>例一、程序运行产生一个窗口，用户关闭的时候需要记录窗口的位置，以便下次运行时保持位置不变；<br>例二、由于程序运行时间很长，今天执行一部分，明天继续执行。那么在下次运行前要恢复前次的状态；<br>... ... ... ...<br><br>智慧的老师：以上这些需求，如何实现呢？<br>懵懂的学生：这个简单，只要在程序退出前提取必要的信息保存到文件中，下次运行时再从文件中读出来，设置一下就OK了。<br>智慧的老师：恩，不错，这位同学的思想值得表扬。<br>懵懂的学生：不好意思，这都要感谢老师的栽培，我对您的景仰如滔滔江水......<br>智慧的老师：别臭P了，我话还没有说完那......如果你需要提取和保存的信息很多，结构很复杂......怎么办？<br>懵懂的学生：也好办，我设计一个结构来记录这些信息。<br>智慧的老师：恩......不错。但如果这些信息提供方是别人写的模块，并且随着版本的不同还经常变化，你怎么办？<br>懵懂的学生：... ...<strong><br></strong>智慧的老师：解决这些问题的方法是---持续性。<strong><br><br>二、原理<br></strong>　　持续性，也叫永久性。组件方提供 IPersistXXX 接口，调用者（容器）提供存储介质，比如文件啦、内存啦、注册表啦、流啦、文本啦......啦啦拉。需要保存的时候，调用者通过 IPersistXXX::Save() 接口函数让组件去自己存储属性信息，而调用者根本不用关心存储格式和存储内容；需要还原状态的时候，调用者打开存储介质，然后同样调用 IPersistXXX::Load() 接口函数让组件自己去读取属性信息并完成初始化的设置。<br>　　目前，微软定义了如下各种类型的持续性接口，足够满足你的需求了。我们只要在自己写的组件中实现其中一个或几个持续性接口，那么调用者就可以按照统一的方式和我们的组件协商完成属性信息的保存和状态还原了。<br>　
<table cellSpacing=1 width="100%" border=1>
    <tbody>
        <tr>
            <td align=middle width="20%"><strong>持续性接口</strong></td>
            <td align=middle width="80%" colSpan=2><strong>简要说明</strong></td>
        </tr>
        <tr>
            <td width="20%">IPersist</td>
            <td width="80%" colSpan=2>&nbsp;　　所有持续性接口的根，下面的接口大多从它派生出来。这个接口很简单，只有一个函数 GetClassID()它返回组件的 CLSID 号，以便调用者能保存这个号为将来 CoCreateInstance() 启动组件用。<br>&nbsp;　　实现这个函数也很简单，只要返回你组件中的 CLSID_XXX 即可，或者比较省事的方法是返回 GetObjectCLSID() 。</td>
        </tr>
        <tr>
            <td width="20%" rowSpan=5>IPersistStream</td>
            <td width="80%" colSpan=2>
            <p align=center>派生自 IPersist，并增加了4个函数，从流(IStream)中读写组件属性信息。</p>
            </td>
        </tr>
        <tr>
            <td width="15%">IsDirty()</td>
            <td width="65%">组件内部属性是否发生了变化。为调用者是否需要保存信息提供依据</td>
        </tr>
        <tr>
            <td width="15%">Load()</td>
            <td width="65%">从 IStream 中读入信息，初始化组件属性</td>
        </tr>
        <tr>
            <td width="15%">Save()</td>
            <td width="65%">把属性信息保存到 IStream 中</td>
        </tr>
        <tr>
            <td width="15%">GetSizeMax()</td>
            <td width="65%">返回信息尺寸，以便调用者事先开辟空间</td>
        </tr>
        <tr>
            <td width="20%">IPersistStreamInit</td>
            <td width="80%" colSpan=2>派生自 IPersistStream，并再增加了一个函数 InitNew() 用来完成一个默认的组件属性初始化。<br>这个持续性接口是最常用的，本文示例中就实现了该接口。</td>
        </tr>
        <tr>
            <td width="20%">IPersistMemory</td>
            <td width="80%" colSpan=2>和 IPersistStreamInit 类似，但使用的是内存块，而不是大小可变化的 IStream 流。</td>
        </tr>
        <tr>
            <td width="20%">IPersistStorage</td>
            <td width="80%" colSpan=2>和 IPersistStream 类似，但保存属性信息使用的是存储 IStorage，一个 IStorage 中可以有多个 IStream。</td>
        </tr>
        <tr>
            <td width="20%">IPersistFile</td>
            <td width="80%" colSpan=2>和 IPersistStream 类似，但存储介质为文件。</td>
        </tr>
        <tr>
            <td width="20%">IPersistPropertyBag</td>
            <td width="80%" colSpan=2>&nbsp;　　使用属性包（属性名、属性值）的文本方式保存信息。在 IE 浏览器中，HTML 嵌入 ActiveX 控件通常使用这个方法。<br>&nbsp;　　在 HTML 中插入控件，&lt;param name="属性名称" value="值"&gt; 这样的形式你应该见过吧？！<br>&nbsp;　　在下一回的文章中，我们介绍这个接口。因为在 ActiveX 中，它太常用了。</td>
        </tr>
        <tr>
            <td width="20%">IPersistPropertyBag2</td>
            <td width="80%" colSpan=2>扩展了 IPersistPropertyBag 接口。提供了更丰富一些的属性管理用函数。</td>
        </tr>
        <tr>
            <td width="20%">IPersistMoniker</td>
            <td width="80%" colSpan=2>用于命名(moniker)存储和读取状态的持续性接口。</td>
        </tr>
        <tr>
            <td width="20%">IPersistHistory</td>
            <td width="80%" colSpan=2>运行于 IE 上，想在用户浏览 WEB 页面时存储和读取状态的持续性接口。</td>
        </tr>
    </tbody>
</table>
</p>
<p><strong>三、持续性接口组件的实现</strong><br>　　示例程序分别在 vc6.0 和 vc.net 上实现了 IPersistStreamInit 接口的 COM 组件和调用举例。组件完成的功能是计算素数，你第一次运行的时候，会得到第一个素数2，然后是3，5，7，11......下班时间到了，今天就运行到这里。于是调用者开辟一个流来保存组件的属性信息。明天继续运行的时候，从流中原换组件状态，开始了新的计算 13，17，19，23......<br>　　这个示例应用完全是假设性的，其实没有什么实用价值，只是演示了 IPersistStreamInit 接口的实现方法。另外，关于建立流(IStream)的方法，请参阅<a href="http://www.vckbase.com/document/viewdoc/?id=1483"><u><font color=#0000ff>《</font></u></a><a href="http://www.vckbase.com/document/viewdoc/?id=1483" target=_blank><u><font color=#0000ff>COM 组件设计与应用（一）》</font></u></a>。<br><br>1、建立一个 ATL 工程项目。<br>2、增加 ATL 组件类，vc.net 使用者注意不要选择&#8220;属性化编程&#8221;方式，其它的设置全部使用默认方法。当然你愿意适当地改变选择也无所谓。<br>3、设计完成你的组件功能。<br>&nbsp;&nbsp; 示例程序中，实现了一个接口函数 GetNext() 负责计算下一个素数。<br>4、添加IPersistStreamInit 接口。</p>
<pre>class ATL_NO_VTABLE Cxxx : 	public CComObjectRootEx&lt;...&gt; ,	public CComCoClass&lt;...&gt;,	......	<strong>public IPersistStreamInit	// 手工添加持续性接口</strong>{......BEGIN_COM_MAP(Cxxx)	......<strong>		// 手工添加接口映射表入口	COM_INTERFACE_ENTRY(IPersistStreamInit)		// 表示如果要取得 IPersistStream 指针，则返回 IPersistStreamInit 指针	COM_INTERFACE_ENTRY_IID(IID_IPersistStream, IPersistStreamInit)		// 表示如果要取得 IPersist 指针，则返回 IPersistStremInit 指针	COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersistStreamInit)</strong>END_COM_MAP()</pre>
5、完成 IPersistStreamInit 接口函数。<br>手工在 h 头文件中增加函数声明：
<pre>public:// IPersist	STDMETHOD(GetClassID)(/*[out]*/CLSID * pClassID);// IPersistStream	STDMETHOD(IsDirty)(void);	STDMETHOD(Load)(/*[in]*/IStream *pStm);	STDMETHOD(Save)(/*[in]*/IStream *pStm,/*[in]*/BOOL fClearDirty);	STDMETHOD(GetSizeMax)(/*[out]*/ULARGE_INTEGER *pcbSize);// IPersistStreamInit	STDMETHOD(InitNew)(void);</pre>
手工在 cpp 文件中增加函数实现：
<pre>// IPersistSTDMETHODIMP Cxxx::GetClassID(/*[out]*/CLSID * pClassID){	*pClassID = GetObjectCLSID();	return S_OK;}// IPersistStreamSTDMETHODIMP Cxxx::IsDirty(void){	if( 数据已经改变，需要保存 )	return S_OK;	else			return S_FALSE;}STDMETHODIMP Cxxx::Load(/*[in]*/IStream *pStm){	return pStm-&gt;Read( 读到哪里, 读多长字节, NULL);}STDMETHODIMP Cxxx::Save(/*[in]*/IStream *pStm,/*[in]*/BOOL fClearDirty){	if( fClearDirty )	清除内部表示数据变化的变量;	return pStm-&gt;Write( 需要保存的数据指针, 写多长字节, NULL );}STDMETHODIMP Cxxx::GetSizeMax(/*[out]*/ULARGE_INTEGER *pcbSize){	pcbSize-&gt;LowPart = 需要保存数据长度的低位;	pcbSize-&gt;HighPart = 需要保存数据长度的高位;// 一般都是0，难道你的数据长度都超过了 4G？	return S_OK;}// IPersistStreamInitSTDMETHODIMP Cxxx::InitNew(void){	内部属性数据默认初始化;	设置或清除内部表示数据变化的变量;	return S_OK;}</pre>
<strong>四、小结</strong><br>　　下载示例程序后，结合本文仔细阅读代码，并试着运行看看效果。如果你理解了，那么你能自己实现 IPersistFile 接口吗？你能自己实现 IPersistStorage 接口吗？<strong>你实现的持续性接口越多，别人使用你的组件就越方便</strong>，也就是说你的组件就能大卖特卖啦，祝你为中国软件事业做贡献的同时多多赚钱:-)下回我们用 IPersistPropertyBag 接口实现持续性属性包功能，别忘了看呦......<br>
<img src ="http://www.cppblog.com/mydriverc/aggbug/29016.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mydriverc/" target="_blank">旅途</a> 2007-07-30 15:59 <a href="http://www.cppblog.com/mydriverc/articles/29016.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>