??xml version="1.0" encoding="utf-8" standalone="yes"?>
公元一?ji)?ji)五年某个夜黑风高的晚上,我的一位老师跟我_“小杨呀Q以后写E序和搭积木一样啦。你赶快学习一些OLE的技术吧......”,当时我心里就L?Q“开什么玩W?搭积木方式写E序Q再q?00q吧......”,但作Z名听话的好学生,我开始在书店里“踅摸”(?Q有关OLE的书c(?Q。功夫不负有心hQ终于买C我的W一本COM书《OLE2 高~程技术》,q本800多页的大布头p了我1/5的月工资呀......于是开始日夜耕读.....
功夫不负有心人,我坚持读完了全部著作Q感xQ这本书Q在说什么呐Q?br /> 功夫不负有心人,我又d了一遍大布头Q感xQ咳~~~Q没懂!
功夫不负有心人,我再Q我?我再?... 感想是:哦~~~Q读懂了一点点啦,哈哈哈?br /> ...... ......
功夫不负有心人,我终于,我终于懂了?br /> 800늚书对现在的我来说Q其实也?0几页有用。到q时候才体会Z么叫“书读薄”的道理了。到后来Q能买到的书也多了,上网也更方便更便宜了......
Z让VCKBASE上的朋友Q不再经历我曄的痛苦、不再重y我“无头苍蝇”般探烦的艰辛、ؓ了VCKBASE的蓬勃发展、ؓ了中国Y件事业的NQ糟p,吹的太也高了Q?.....我打节U一些在 BBS 上赚分的旉Q写个系列论文,叫“COMlg设计与应用”吧。今天是W一部分——v源?br />
二、文件的存储
传说350q前Q牛被Ҏ(gu)砸到了头Q于是发C万有引力。但C二十一世纪的现在,M一个技术的发明和发展,已经不再依靠圣h灵光的一闪。技术的q步转而是被社会的需求、商业的利益、竞争的压力、行业的渗透等推动的。微软在Windowsq_上的lg技术也不例外,它的发明Q有其必然因素。什么是q个因素那?{案是——文件的存储?br /> 打开C本程序,输入了一文章后Q保存。——这L文g叫“非l构化文件”;
打开?sh)子表格E序Q输入一个班的学生姓名和考试成WQ保存。——这L文g叫“标准结构化文g”;
在我们写的程序中Q需要把特定的数据按照一定的l构和顺序写到文件中保存。——这L文g叫“自定义l构化文件”;Q比?*.bmp 文gQ?br /> 以上三种cd的文Ӟ大家都见的多了。那么文件存储就依靠上述的方式能满所有的应用需求吗Q恩~~~Q至从计算机发明后?0多年来,一直是够用的了。嘿嘿,下面看看商业利益的推动作用,Ҏ(gu)?的存储Ş式生了什么变化吧?0岁以上的朋友Q我估计以前都用过以下几个著名的YӞWordStarQ独霸DOS下的英文~辑软gQ,WPSQ裘伯君写的中文~辑软gQ据说当q的市场占有率高?0%Q各U计机培训班的必修评Q,LOTUS-123Q莲花公司出品的?sh)子表格软gQ?.....
微Y在成功地推出 Windows 3.1 后,开始垂涎桌面办公自动化软g领域。微软的 OFFICE 开发部门,各小l分别独立地开发了 WORD ?EXCEL {YӞq用“自定义l构”方式,Ҏ(gu)件进行存储。在Ȁ烈的市场竞争下,Z打|竞争Ҏ(gu)Q微软自然地产生了一个念?-----如果我能?WORD E序中嵌?EXCELQ那么用户在购买了我 WORD 软g的情况下Q不没有必要再?LOTUS-123 了吗Q!“恶毒”(中国微Y的同志们看到了这个词Q不要激动,我是加了引号的呀Q的计划产生后,他们开始了实施工作Q这是 COM 的前w?OLE 的v源(?Q。但立刻遇C一个严重的技术问题:需要把 WORD 产生?DOC 文g?EXCEL 产生?XLS 文g保存在一赗?br />
Ҏ(gu) |
优点 |
~点 |
建立一个子目录Q把 DOC、XLS 存储在这同一个子目录中?/td> | 数据隔离性好QWORD 不用了解 EXCEL 的存储结构;Ҏ(gu)扩展?/td> | l构太松散,Ҏ(gu)造成数据的损坏或丢失?br />不易携带?/td> |
修改文g存储l构Q在DOCl构基础上扩展出包容 XLS 的结构?/td> | l构紧密Q容易携带和l一理?/td> | WORD 的开发h员需要通晓 EXCEL 的存储格式;~少扩展性,M能新加一个类型就扩展一下结构吧Q! |
以上两个Ҏ(gu)Q都有严重的~陷Q怎么解决那?如果能有一个新Ҏ(gu)Q能够合q前两个Ҏ(gu)的优点,消灭~点Q该多好呀......微Y是作盘***作系lv家的Q于是很自然C们提Z一个非常完的设计Ҏ(gu)Q那是把磁盘文件的理方式UL到文件中?-----复合文gQ俗U“文件中的文件系l”。连微Y当年都没有想刎ͼp么一个简单的xQ居然最后就演变Z COM lgE序设计的方法。可以说Q复合文件是 COM 的基矟뀂下图是盘文gl织方式与复合文件组l方式的cL图:
图一、左侧表CZ个磁盘下的文件组l方式,右侧表示一个复合文件内部的数据l织方式?br />
三、复合文件的特点
四、浏览复合文?/b>
VC6.0 附带了一个工兯Y件“复合文件浏览器”,文g名是“vc目录\Common\Tools\DFView.exe”。ؓ了方便用该E序Q可以把它加到工?tools)菜单中。方法是QTools\Customize...\Tools卡片中增加新的项目。运?DFView.exeQ就可以打开一个复合文件进行观察了Q注4Q。但奇怪的是,?Microsoft Visual Studio .NET 2003 中,我反而找不到q个工具E序?汗!不过q恰好提供给大家一个练习的ZQ在你阅d本篇文章q掌握了~程Ҏ(gu)后,自己写一个“复合文件浏览编辑器”程序,又练手了Q还有实用的价倹{?br />
?/b>、复合文件函?/b>
复合文g的函数和盘目录文g?**作非常类伹{所有这些函敎ͼ被分?U类型:WIN API 全局函数Q存?IStorage 接口函数Q流 IStream 接口函数。什么是接口Q什么是接口函数Q以后的文章中再陆箋介绍Q这里大家只要把“接口”看成是完成一l相?**作功能的函数集合可以了?br />
WIN API 函数 |
功能说明 |
StgCreateDocfile() | 建立一个复合文Ӟ得到根存储对?/td> |
StgOpenStorage() | 打开一个复合文Ӟ得到根存储对?/td> |
StgIsStorageFile() | 判断一个文件是否是复合文g |
|
|
IStorage 函数 |
功能说明 |
CreateStorage() | 在当前存储中建立新存储,得到子存储对?/td> |
CreateStream() | 在当前存储中建立新流Q得到流对象 |
OpenStorage() | 打开子存储,得到子存储对?/td> |
OpenStream() | 打开,得到对?/td> |
CopyTo() | 复制存储下的所有对象到目标存储中,该函数可以实现“整理文Ӟ释放片I间”的功能 |
MoveElementTo() | Ud对象到目标存储中 |
DestoryElement() | 删除对象 |
RenameElement() | 重命名对?/td> |
EnumElements() | 枚D当前存储中所有的对象 |
SetElementTimes() | 修改对象的时?/td> |
SetClass() | 在当前存储中建立一个特D的对象,用来保存CLSIDQ注5Q?/td> |
Stat() | 取得当前存储中的pȝ信息 |
Release() | 关闭存储对象 |
IStream 函数 |
功能说明 |
Read() | 从流中读取数?/td> |
Write() | 向流中写入数?/td> |
Seek() | 定位d位置 |
SetSize() | 讄尺寸。如果预先知道大,那么先调用这个函敎ͼ可以提高性能 |
CopyTo() | 复制数据到另一个流对象?/td> |
Stat() | 取得当前中的系l信?/td> |
Clone() | 克隆一个流对象Q方便程序中的不同模?**作同一个流对象 |
Release() | 关闭对?/td> |
WIN API 补充函数 | 功能说明 |
WriteClassStg() | 写CLSID到存储中Q同IStorage::SetClass() |
ReadClassStg() | dWriteClassStg()写入的CLSIDQ相当于化调用IStorage::Stat() |
WriteClassStm() | 写CLSID到流的开始位|?/td> |
ReadClassStm() | dWriteClassStm()写入的CLSID |
WriteFmtUserTypeStg() | 写入用户指定的剪贴板格式和名U到存储?/td> |
ReadFmtUserTypeStg() | dWriteFmtUserTypeStg()写入的信息。方便应用程序快速判断是否是它需要的格式数据?/td> |
CreateStreamOnHGlobal() | 内存句柄 HGLOBAL 转换为流对象 |
GetHGlobalFromStream() | 取得CreateStreamOnHGlobal()调用中用的内存句柄 |
Z让大家快速地览和掌握基本方法,上面所列表的函数ƈ不是全部Q我省略了“事务”函数和未实现函数部分。更全面的介l,请阅?MSDN?br /> 下面E序片段Q演CZ一些基本函数功能和调用Ҏ(gu)。?
CZ一Q徏立一个复合文Ӟq在其下建立一个子存储Q在该子存储中再建立一个流Q写入数据?/p>
void SampleCreateDoc() { ::CoInitialize(NULL); // COM 初始? // 如果是MFCE序Q可以用AfxOleInit()替代 HRESULT hr; // 函数执行q回? IStorage *pStg = NULL; // 根存储接口指? IStorage *pSub = NULL; // 子存储接口指? IStream *pStm = NULL; // 接口指? hr = ::StgCreateDocfile( // 建立复合文g L"c:\\a.stg", // 文g名称 STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, // 打开方式 0, // 保留参数 &pStg); // 取得根存储接口指? ASSERT( SUCCEEDED(hr) ); // ZH出重点Q简化程序结构,所以用了断言? // 在实际的E序中则要用条件判断和异常处理 hr = pStg->CreateStorage( // 建立子存? L"SubStg", // 子存储名U? STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0,0, &pSub); // 取得子存储接口指? ASSERT( SUCCEEDED(hr) ); hr = pSub->CreateStream( // 建立? L"Stm", // 名U? STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0,0, &pStm); // 取得接口指? ASSERT( SUCCEEDED(hr) ); hr = pStm->Write( // 向流中写入数? "Hello", // 数据地址 5, // 字节长度(注意Q没有写入字W串l尾的\0) NULL); // 不需要得到实际写入的字节长度 ASSERT( SUCCEEDED(hr) ); if( pStm ) pStm->Release();// 释放指? if( pSub ) pSub->Release();// 释放子存储指? if( pStg ) pStg->Release();// 释放根存储指? ::CoUninitialize() // COM 释放 // 如果使用 AfxOleInit(),则不调用该函? }
Qi nclude六、小l?/b>// ANSI、MBCS、UNICODE 转换 void SampleEnum() { // 假设你已l做q?COM 初始化了 LPCTSTR lpFileName = _T( "c:\\a.stg" ); HRESULT hr; IStorage *pStg = NULL; USES_CONVERSION; // Q注6Q? LPCOLESTR lpwFileName = T2COLE( lpFileName ); // 转换Tcd为宽字符 hr = ::StgIsStorageFile( lpwFileName ); // 是复合文件吗Q? if( FAILED(hr) ) return; hr = ::StgOpenStorage( // 打开复合文g lpwFileName, // 文g名称 NULL, STGM_READ | STGM_SHARE_DENY_WRITE, 0, 0, &pStg); // 得到根存储接口指? IEnumSTATSTG *pEnum=NULL; // 枚D? hr = pStg->EnumElements( 0, NULL, 0, &pEnum ); ASSERT( SUCCEEDED(hr) ); STATSTG statstg; while( NOERROR == pEnum->Next( 1, &statstg, NULL) ) { // statstg.type 保存着对象cd STGTY_STREAM ?STGTY_STORAGE // statstg.pwcsName 保存着对象名称 // ...... q有旉Q长度等很多信息。请查看 MSDN ::CoTaskMemFree( statstg.pwcsName ); // 释放名称所使用的内存(?Q? } if( pEnum ) pEnum->Release(); if( pStg ) pStg->Release(); }
Revision History:
Version Date Creator Description
Implementation ScopeQ?/p>
l箋阅读之前Q我们假设?zhn)熟(zhn)以下知识Q?/p>
目录Q?/p>
1:概述
2:借用SAFEARRAY打包把结构写入MSMQ队列
3:借用IStream打包传递数据到MSMQ队列
1.概述
通常我们通过MSMQ传递基于XML的字W串Q但有时候也需要传递一些结构或者一些接口指针,那么如何打包传递呢Q?/p>
q实际上可以转换Z个普适问题:
如何把一个结构体(structure object)或者巨大内存块Q比?MB左右Q打包ؓPROPVARIANT-compatible的类型?
首先QIMSMQMessagePtr的Body属性接收_variant_t参数:
inline void IMSMQMessage::PutBody ( const _variant_t & pvarBody ) {
HRESULT _hr = put_Body(pvarBody);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
}
如果我们xl构作ؓ消息的Body写入MSMQ消息队列Q我们需要把我们的结构、大内存块或接口指针转换为_variant_t?/p>
2.借用SAFEARRAY打包把结构写入MSMQ队列
把一个结构体打包为PROPVARIANT-compatible的类型,需要用到SAFEARRAYQ一个带有边界信息的数组。这是一个常用技巧,很多文章都有提及Q我׃多解释了?/p>
但是Q注意这U方式一ơ只能打?5536字节以下的数据,q是׃
SAFEARRAY* SafeArrayCreateVector(
VARTYPE vt,
long lLbound,
unsigned int cElements);
的定义所限制的?/p>
我们通常会用SafeArrayCreateVector API创徏一个单lSAFEARRAYQ分配一个sizeof(_DATA)大小的连l内存块Q而这个函数的W三个参数是一个unsigned intcdQ所以最大值就只能?5536了?/p>
更多SAFEARRAY知识Q参见用SAFEARRAY传递对象?/p>
2.借用SAFEARRAY打包把结构写入MSMQ队列
l上1.1的打包步骤QVC++代码Q:
// ChangeStruct2Var函数的定义:
// W一个参敎ͼ
// cdQCComVariant
// 作用Q接收?/p>
// W二个参敎ͼ
// cdQ_DATA*
// 作用Q源
HRESULT ChangeStruct2Variant (CComVariant &var, _DATA *pData)
{
HRESULT hr = S_OK;
// 使用SafeArrayCreateVector API创徏一个单lSAFEARRAYQ分配一个sizeof(_DATA)大小的连l内存块
// VT--UI1代表非负整Ş的变量类型,1个字?/p>
// 常数'0'定义数组的下?/p>
LPSAFEARRAY lpsa = SafeArrayCreateVector(VT_UI1, 0, sizeof(_DATA));
LPBYTE pbData = NULL;
if (lpsa)
{
//在你讉KSAFEARRAY数据之前Q你必须调用SafeArrayAccessData。该函数锁定数据q且q回一个指针。在q里Q锁定数l意味着增加该数l的内部计数器(cLocksQ?/p>
hr = SafeArrayAccessData(lpsa, (void **)&pbData);
}
else
hr = HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// 使用safe arrayQ?/p>
// 传入的_DATA指针指向的内存复制到pbData
CopyMemory(pbData, pData, sizeof(*pData));
// 讄var的类型ؓ数组
var.vt = VT_ARRAYVT_UI1;
// var和我们的单维SAFEARRAY拉上关系Q?/p>
var.parray = lpsa;
}
if (pbData)
{
//相应用来释放数据的函数是SafeArrayUnaccessData()Q该功能释放该参数的计数?/p>
SafeArrayUnaccessData(var.parray);
}
if (FAILED(hr))
{
// 销毁SAFEARRAY
SafeArrayDestroy(lpsa);
}
return hr;
}
////////////////////////////////////////////////////////////
//Added Headers:
////////////////////////////////////////////////////////////
#include <comdef.h>
#include <atlbase.h>
///////////////////////////////////////////////////////////
//Added for MSMQ:
///////////////////////////////////////////////////////////
#import "mqoa.dll" no_namespace, named_guids
typedef struct _DATA
{
int _n;
char _str;
}_DATA;
//main:
{
.. ..
.. ..
IMSMQMessagePtr pisMsg = NULL;
hr = pisMsg.CreateInstance("MSMQ.MSMQMessage");
_DATA msg;
msg._n = 1;
msg._str = '1';
CComVariant var;
// 打包函数Q?/p>
ChangeStruct2Variant(var, &msg);
// 打包后的CComVariant传递给MSMQMessege的Body属性:
pisMsg->Body= var;
pisMsg->AppSpecific=-1;
// 发送到消息队列Q?/p>
pisMsg->Send(pisQueue);
.. ..
}
q样Q就可以成功地把一个结构递交到MSMQ队列中了?/p>
Implementation ScopeQ?br>l箋阅读之前Q我们假设?zhn)熟(zhn)以下知识Q?/p>
目录Q?/p>
1:概述
2:借用SAFEARRAY打包把结构写入MSMQ队列
3:借用IStream打包传递数据到MSMQ队列
下面l出dMSMQ消息时解析的步骤QVC++代码Q:
////////////////////////////////////////////////////////////
//Added Headers:
////////////////////////////////////////////////////////////
#include <comdef.h>
#include <atlbase.h>
///////////////////////////////////////////////////////////
//Added for MSMQ:
///////////////////////////////////////////////////////////
#import "mqoa.dll" no_namespace, named_guids
typedef struct _DATA
{
int _n;
char _str;
}_DATA;
//main:
{
.. ..
.. ..
hr = pisQI->raw_Open(MQ_PEEK_ACCESS,MQ_DENY_NONE,&pisQueue);
IMSMQMessagePtr piMessage;
// 获取MSMQ队列中的一个消息:
piMessage = pisQueue->PeekCurrent();
_DATA *msg = new _DATA();
// 解析函数Q?/p>
ChangeVariant2Struct(CComVariant(piMessage->Body), msg);
.. ..
}
// ChangeVariant2Struct函数的定义:
// W一个参敎ͼ
// cdQCComVariant
// 作用Q源
// W二个参敎ͼ
// cdQ_DATA*
// 作用Q接收?/p>
HRESULT ChangeVariant2Struct (CComVariant &var, _DATA *DP)
{
SAFEARRAY* psa;
BYTE HUGEP *lpb;
psa = var.parray;
SafeArrayAccessData(psa, (void HUGEP **)&lpb);
CopyMemory((LPVOID)DP, (LPVOID)lpb, 8);
SafeArrayUnaccessData(psa);
return S_OK;
}
Writen by zhengyun.NoJunk(at)tomosoft.dot.com
DisclaimersQ?br>
本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyunҎ(gu)讨论问题的当前看法,zhengyun不保证所l信息在发布之日以后的准性?
用户应清楚本文档的准性及其用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款Q?
复制时不得修改原文,复制内容d含所有页 Q?
所有副本均d?zhengyun的版权声明以及所提供的其它声?Q?/p>
Implementation ScopeQ?br>l箋阅读之前Q我们假设?zhn)熟(zhn)以下知识Q?/p>
目录Q?/p>
1:概述
2:借用SAFEARRAY打包把结构写入MSMQ队列
3:借用IStream打包传递数据到MSMQ队列
3.借用IStream传递数?br>正如前面所qͼ当你有一块非常巨大的数据要传递给MSMQ队列Ӟ而且你希望一ơ液压成型,那么把它打包入IStream,也是一个很常用技巧,了解COM的h都知道,我也不多解释了?/p>
我们研究了ATL中IPersistMemoryImpl接口LoadҎ(gu)的实现机理,来做我们的事情:
// 函数名:LoadStreamOnHugeMemory
// 功能Q?nbsp; 同上
// W一个参数pvMem指向一块要打包的内存,W二个参数指明这块内存的大小
HRESULT LoadStreamOnHugeMemory(void pvMem, ULONG cbSize)
{
// Get Memory Handle:
HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, cbSize);
If(NULL == h) return E_OUTOFMEMORY;
LPVOID pv = GlobalLock(h);
If(!pv) return E_OUTOFMEMORY;
// Copy to memory block
CopyMemory(pv, pvMem, cbSize);
CComPtr<IStream> spStream;
// Create stream on Memory:
HRESULT hr = CreateStreamOHGlobal(h, TRUE, &spStream);
If(FAILED(hr))
{
GlobalUnlock(h);
GlobalFree(h);
return hr;
}
// stream now owns the memory
// unlock the data
GlobalUnlock(hGlobal);
// Create a stream holder. Load the stream holder from the global stream.
// THIS STREAM HOLDER IS INTERITED FROM IPersistStream
// And all virtual functions are Modified to handle the object....
CComPtr <IStreamHolder> pHolder = new CComObject <CStreamHolder>;
CComPtr <IPersistStream> pHolderStream;
hr = pHolder->QueryInterface (IID_IPersistStream, (void **)&pHolderStream);
pStream->Seek( zero, STREAM_SEEK_SET, NULL );
pHolderStream->Load(pStream);
CComVariant vComData = pHolder;
.. ..
//
// now, you have a big chunk of memory loaded into a ComVariant
//
// 现在你可以把打包后的CComVariant传递给MSMQMessege的Body属性了Q?/p>
pisMsg->Body = vComData;
.. ..
}
其实QAydin的实现和ATL中IPersistMemoryImpl接口LoadҎ(gu)实现异曲同工。我们不妨换一U方式实现?/p>
Writen by zhengyun.NoJunk(at)tomosoft.dot.com
DisclaimersQ?br>本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyunҎ(gu)讨论问题的当前看法,zhengyun不保证所l信息在发布之日以后的准性?
用户应清楚本文档的准性及其用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款Q?
复制时不得修改原文,复制内容d含所有页 Q?
所有副本均d?zhengyun的版权声明以及所提供的其它声?Q?
不得以赢利ؓ目的Ҏ(gu)文档q行传播 ?/p>
Implementation ScopeQ?br>l箋阅读之前Q我们假设?zhn)熟(zhn)以下知识Q?/p>
目录Q?/p>
1:概述
2:借用SAFEARRAY打包把结构写入MSMQ队列
3:借用IStream打包传递数据到MSMQ队列
其实QAydin的实现和ATL中IPersistMemoryImpl接口LoadҎ(gu)实现异曲同工。我们不妨换一U方式实现?/p>
Aydinl出的VC++代码是:
// 指?/p>
CComPtr<IStream> pStream = NULL;
LARGE_INTEGER zero = {0,0};
// hGlobal是内存句柄:
LPBYTE pChunk = (BYTE *) GlobalLock(hGlobal);
// 创徏一个空的streamQ?/p>
HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream );
pStream->Seek( zero, STREAM_SEEK_SET, NULL );
ULONG pcbWritten = 0;
// pChunk现在已经指向我们的巨大内存块Q?/p>
// 我们把这块内存写入IStream中Q?/p>
pStream->Write (pChunk, dwNumRead, &pcbWritten);
// 查是否全部写入了Q?/p>
ATLASSERT(pcbWritten==dwNumRead);
// unlock the data
GlobalUnlock(hGlobal);
// 剩下的一P也是让CComPtr <IPersistStream>来调用LoadҎ(gu)加蝲IStream
// Create a stream holder. Load the stream holder from the global stream.
// THIS STREAM HOLDER IS INTERITED FROM IPersistStream
// And all virtual functions are Modified to handle the object....
CComPtr <IStreamHolder> pHolder = new CComObject <CStreamHolder>;
CComPtr <IPersistStream> pHolderStream;
hr = pHolder->QueryInterface (IID_IPersistStream, (void **)&pHolderStream);
pStream->Seek( zero, STREAM_SEEK_SET, NULL );
pHolderStream->Load(pStream);
CComVariant vComData = pHolder;
.. ..
//
// now, you have a big chunk of memory loaded into a ComVariant
//
// 现在你可以把打包后的CComVariant传递给MSMQMessege的Body属性了Q?/p>
pisMsg->Body = vComData;
.. ..
//Coder: Aydin T.BAKIR
全文完?/p>
Writen by zhengyun.NoJunk(at)tomosoft.dot.com
DisclaimersQ?br>
本文档仅供参考。本文档所包含的信息代表了在发布之日,zhengyunҎ(gu)讨论问题的当前看法,zhengyun不保证所l信息在发布之日以后的准性?
用户应清楚本文档的准性及其用可能带来的全部风险。可以复制和传播本文档,但须遵守以下条款Q?
复制时不得修改原文,复制内容d含所有页 Q?
所有副本均d?zhengyun的版权声明以及所提供的其它声?Q?
不得以赢利ؓ目的Ҏ(gu)文档q行传播 ?/p>
Windows区对?Bands)的创Z定制
~译/赉|?/font>
1 ?/font> 1.1 览栏区对象 1.2 工具栏区对象 1.3 必须实现的接?/font> 3.3.1 IUnknown 3.3.2 IObjectWithSite 3.3.3 IPersistStream 3.3.4 IDeskBand 3.4 一??/a> Windows的区QBandsQ对象有三种Q既览栏(Explorer BarQ区对象Q工hQTools BandsQ区对象Q和桌面区对象(Desk BandsQ?br> 览栏区对象 览栏区对象U浏览栏Q它是从IE4.0引入的,它是邻近览器窗格的一个显C区域。实际上它是IEH口中的一个子H口Q可以用它来昄信息及与用户交互。浏览栏卛_以是以垂直方式定位在览器窗格的左边。也可以水^方式定位在浏览器H格下面。(如图一Q? |
图一 在浏览栏中可以创建很多子菜单或选项Q用戯以不同方式选择q些子菜单或选项提供的功能,打开IE或者资源管理器Q从“查看”菜单中选择“览?#8221;Q可以看到Windows提供了几U标准的览栏菜单,?#8220;搜烦QSearchQ?#8221;,“收藏夹(FavoritesQ?#8221;Q??#8220;历史记录QHistoryQ?#8221;,以及“文g夹(All FoldersQ?#8221;。(如图二) |
图二 Z创徏定制的浏览栏Q必ȝE实玎ͼ然后注册它们。Windows在外壻IShellQ?.71中引入了区对象。它提供与普通窗口一L功能。但因ؓ它是以IE或外壳ؓ容器的COM对象Q所以实现v来就与普通窗口有所不同。图一中显C的是一个简单的览栏例子。图中有一个垂直的览栏和一个水q的览栏?br> 工具栏区对象 工具栏区对象U工hQ它是在IE5.0中引入用以支持单选工hQradio toolbarQ特性的。IE工具栏实际上是一个Rebar控gQ它包含了几个工hQtoolbarQ控件。通过创徏工具栏,你可以将某个区对象功能添加到Rebar控g中。不论是在IE中还是在资源理器中Q区对象都是一LQ所以工h也是一个通用H口。(如图三) ![]() 图三 用户可以?#8220;查看”菜单中的“工具?#8221;子菜单中选择昄单选工hQ也可以在工h区域单击鼠标右键从它的上下文菜单中选择昄单选工h?br> 区对象也可以用在桌面Q也是创徏桌面区对象。虽然它们的基本实现与浏览栏cMQ但桌面ZIE没有关系Q它不用IE作ؓ容器。它主要用来创徏桌面动H口。通过在Q务栏上单d键,然后在弹出的菜单中选择“工具?#8221;的子菜单选项。(如图四) ![]() 囑֛ 桌面区的初始动位置在Q务栏Q(如图?br> ![]() 图五 用户可以桌面区拖到桌面上,q时它就成了一个普通窗口:Q如囑օQ?br> ![]() 囑օ |
二、实现区对象 管可以像用普通窗口一样用区对象Q但它们毕竟是COM对象Q存在于某个容器之中。如览栏和工具栏位于IE之中Q桌面区位于外壳之中。虽然它们的功能不同Q但其基本实现非常相伹{一个主要的差别是它们的注册方式不同Q而注册方式的不同又决定了对象的类型及其容器。这一部分我们先讨论所有区对象实现的共性。其它的实现l节可参?a target=_self>垂直览栏例子程?/font>?br>区对象除了要实现 IUnknown ?IClassFactory 两个接口之外Q所有的区对象还必须实现以下q几个接口:
对于如何注册区对象的q一步讨参见注册部分?br>如果某个区对象接受用戯入,它还必须实现IInputObject接口。如果要往上下文菜单中d菜单目Q还必须实现IContextMenu接口。注意:工具栏区对象不支持上下文菜单?br> 因ؓ区对象实现的是子H口Q所以它们还必须有窗口过E来处理Windows的消息?br> 区对象可以通过其IOleCommandTarget接口发送命令到它的容器。ؓ了得到这个接口的指针Q必调用容器的IInputObjectSite::QueryInterfaceҎ(gu) DBID_BANDINFOCHANGED——Band的信息已改变。参数pvaIn的值应该是最q一ơ调用所用的band标示W。容器将调用q个标示W所指的band对象的IDeskBand::GetBandInfoҎ(gu)h更新的信息?br>DBID_MAXIMIZEBAND——容器将最大化band。参数pvaIn的值应该是最q一ơ调用所用的band标示W?br>DBID_SHOWONLY——关闭或打开容器中其它band。参数pvaIn的gؓVT_UNKNOWNcdQ可以取下列g一Q?br>
注册 区对象必ME内服务器(in-processQ注册。其U程模型必须?#8220;Apartment”。也是说区对象必须以DLL的Ş式来实现。用来描q服务器注册条目的缺省值是一个菜单文本串。就拿浏览栏来说。这个菜单出现在资源理器或IE “查看QViewQ?#8221;菜单?#8220;览栏(Explorer BarQ?#8221;子菜单中。而工h的菜单则出现在资源管理器或IE “查看QViewQ?#8221;菜单?#8220;工具栏(ToolbarsQ?#8221;子菜单中。桌面区出现在Q务栏上下文菜单的“工具栏(ToolbarsQ?#8221;子菜单中。作单资源,提供键盘快捷的方法与一般菜单快捷键相同。也是?#8220;&”字符攑֜某个单词字母前表C个字母显CZ划线来指C快捷键?br>通常区对象的注册条目如下: HKEY_CLASSES_ROOT ... CLSID ... {Band 对象?CLSID GUID} = "菜单文本? InProcServer32 = "DLL 路径? ThreadingModel = "Apartment"工具栏区对象必须q要注册对象的CLSID。ؓ此必dHKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar下创Z个REG_SZ|用工h区对象的CLSID GUID串命名。如Q?br> HKEY_LOCAL_MACHINE Software Microsoft Internet Explorer Toolbar { Band 对象?CLSID GUID } 除此之外Q还有几个可选的注册值可以加到注册表中,本文的例子中未用这些倹{?
HKEY_CLASSES_ROOT ... CLSID ... {Band 对象?CLSID GUID} = "菜单文本? InProcServer32 = "DLL 路径? ThreadingModel = "Apartment" Instance CLSID = "{4D5C8C2A-D075-11D0-B416-00C04FB90376}" InitPropertyBag Url = "HTML文g" ... HKEY_CURRENT_USER ... Software ... Microsoft ... Internet Explorer ... Explorer Bars { Band 对象?CLSID GUID } BarSize = "23,01,00,00,00,00,00,00" 你可以通过~程的方式来处理区对象类?CATID 的注册。创Z个组件类别管理器对象(CLSID_StdComponentCategoriesMgr)q请求一个指向ICatRegister接口的指针。将区对象的CLSID和CATID传递到ICatRegister::RegisterClassImplCategories?br>三、定制浏览栏的一个简单例?/a> q个例子展示了前面所介绍q的垂直览栏的整个实现q程。它借助了^台SDKQPlatform SDK——在msdn中可以找刎ͼ中关于band对象C代码。其中还包括了水qx览栏和桌面band的实C码。详l实现细节请参见QCommBand.cpp和DeskBand.cpp?br>创徏定制览栏的基本q程是这LQ?br>
DLL函数 所有三U区对象被打包在一个DLL中,它输Z下的函数Q?br>
注册定制的浏览栏 有了COM对象后,必须Ҏ(gu)览栏的CLSIDq行注册。另外如果要与IE或资源管理器 协调q行Q还必须q行的恰当的lgU类QCATID_InfoBandQ注册。这个工作由DllRegisterServer处理。浏览栏例子代码有关的处理部分如下: ... //注册览栏对? if(!RegisterServer(CLSID_SampleExplorerBar, TEXT("垂直览栏例?))) return SELFREG_E_CLASS; //注册览栏的对象lgU类 if(!RegisterComCat(CLSID_SampleExplorerBar, CATID_InfoBand)) return SELFREG_E_CLASS; ...区对象的注册使用通常的COMq程Q它q有函数RegisterServer处理?br>除了CLSID之外Q这个区对象服务器还必须注册一个以上的lgU类。这实际上是垂直览栏和水^览栏实C间的主要差别。这个过E的处理是通过创徏一个组件种cȝ理器对象QCLSID_StdComponentCategoriesMgrQ,q用ICatRegister::RegisterClassImplCategoriesҎ(gu)来注册区对象服务器。在q个例子中,lgU类注册的处理是通过浏览栏的CLSID和CATID传递到U有函数RegisterComCat完成的: 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*)&pcr); if(SUCCEEDED(hr)) { hr = pcr->RegisterClassImplCategories(clsid, 1, &CatID); pcr->Release(); } CoUninitialize(); return SUCCEEDED(hr); } |
垂直览栏例子实C四个必须的接口:IUnknown, IObjectWithSite, IPersistStream, 和IDeskBandQ它们都在CExplorerBarcM实现?br>IUnknown 构造函敎ͼ析构函数和IUnknown实现比较单,本文在此不讨论。细节请参见源代码?br>IObjectWithSite接口 当用户选择某个览栏时Q容器调用相应band对象的IObjectWithSite::SetSiteҎ(gu)。参数将被设|成q个现场QSiteQ的IUnknown指针?br>通常QSetSite实现应该完成下列步骤Q?br>
STDMETHODIMP CExplorerBar::SetSite(IUnknown* punkSite) { //如果某个现场被把持,则释攑֮ if(m_pSite) { m_pSite->Release(); m_pSite = NULL; } //如果punkSite 不ؓNULL, 建立一个新的现? if(punkSite) { //获取父窗? IOleWindow *pOleWindow; m_hwndParent = NULL; if(SUCCEEDED(punkSite->QueryInterface(IID_IOleWindow, (LPVOID*)&pOleWindow))) { pOleWindow->GetWindow(&m_hwndParent); pOleWindow->Release(); } if(!m_hwndParent) return E_FAIL; if(!RegisterAndCreateWindow()) return E_FAIL; //获取柄保存I(y)InputObjectSite指针 if(SUCCEEDED(punkSite->QueryInterface(IID_IInputObjectSite, (LPVOID*)&m_pSite))) { return S_OK; } return E_FAIL; } return S_OK; }q个例子的GetSite只简单地用SetSite保存的现场指针实C对现场QueryInterfaceҎ(gu)的调用?br> STDMETHODIMP CExplorerBar::GetSite(REFIID riid, LPVOID *ppvReturn) { *ppvReturn = NULL; if(m_pSite) return m_pSite->QueryInterface(riid, ppvReturn); return E_FAIL; }H口创徏q有方法RegisterAndCreateWindow负责。如果这个窗口不存在Q此Ҏ(gu)浏览栏H口创徏成一个大适当的子H口Q它的父H口是由SetSite获得的那个窗口。子H口的句柄存储在m_hwnd变量中?br> BOOL CExplorerBar::RegisterAndCreateWindow(void) { //如果q个H口不存在,则创建它 if(!m_hWnd) { //子窗口不能没有父H口 if(!m_hwndParent) { return FALSE; } //如果H口cL有注册,则必L? WNDCLASS wc; if(!GetClassInfo(g_hInst, EB_CLASS_NAME, &wc)) { ZeroMemory(&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(&wc)) { //如果注册p|Q下面的CreateWindow函数失? } } RECT rc; GetClientRect(m_hwndParent, &rc); //创徏q个H口。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); }IPersistStream接口 IE调用浏览栏的IPersistStream接口Q以便允许这个浏览栏加蝲或存储持久性数据。如果没有持久性数据,q个Ҏ(gu)仍然必须q回一个成功代码。IPersistStream接口从IPersistl承而来Q所以要实现五个Ҏ(gu)Q?br>GetClassID, IsDirty, Load, Save, GetSizeMax?br>本文的这个浏览栏例子不用持久性数据,q且只有IPersistStream的最实现。GetClassIDq回对象的CLSIDQCLSID_SampleExplorerBarQ,其余的方法返回S_OK, 或者S_FALSE, 或?E_NOTIMPL。有关细节请参见IPersistStream的实现?br> IDeskBand接口 IDeskBand接口是区对象专用接口。它只有一个方法。IDeskBand接口从IDockingWindowl承而来Q而IDockingWindow又从IOleWindowl承而来?br>IOleWindow有两个方法:GetWindow ?ContextSensitiveHelp。浏览栏例子的GetWindow实现q回览栏的子窗口句柄m_hwnd。因Z实现上下文敏感帮助,所以ContextSensitiveHelpq回E_NOTIMPL?br>IDockingWindow接口有三个方法:ShowDW, CloseDW, ?ResizeBorder。ResizeBorder不在M区对象中使用Q应该返回E_NOTIMPL。ShowDWҎ(gu)Ҏ(gu)其不同的参数值控制浏览栏H口的显C或隐藏Q?br> STDMETHODIMP CExplorerBar::ShowDW(BOOL fShow) { if(m_hWnd) { if(fShow) { //昄H口 ShowWindow(m_hWnd, SW_SHOW); } else { //隐藏H口 ShowWindow(m_hWnd, SW_HIDE); } } return S_OK; } CloseDWҎ(gu)摧毁览栏窗口: STDMETHODIMP CExplorerBar::CloseDW(DWORD dwReserved) { ShowDW(FALSE); if(IsWindow(m_hWnd)) DestroyWindow(m_hWnd); m_hWnd = NULL; return S_OK; }其余的方法,如GetBandInfo是IDeskBand专用的。IE使用它来指定览栏的标示W以及视图模式。IEq可能填写DESKBANDINFOl构的dwMask成员从浏览栏h更多的信息,q个l构用第三个参数传递。GetBandInfo应该存储q个标示W和视图模式q用所h的数据填写DESKBANDINFOl构。下面是本文览栏例子所实现GetBandInfoQ?br> STDMETHODIMP CExplorerBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi) { if(pdbi) { m_dwBandID = dwBandID; m_dwViewMode = dwViewMode; if(pdbi->dwMask & DBIM_MINSIZE) { pdbi->ptMinSize.x = MIN_SIZE_X; pdbi->ptMinSize.y = MIN_SIZE_Y; } if(pdbi->dwMask & DBIM_MAXSIZE) { pdbi->ptMaxSize.x = -1; pdbi->ptMaxSize.y = -1; } if(pdbi->dwMask & DBIM_INTEGRAL) { pdbi->ptIntegral.x = 1; pdbi->ptIntegral.y = 1; } if(pdbi->dwMask & DBIM_ACTUAL) { pdbi->ptActual.x = 0; pdbi->ptActual.y = 0; } if(pdbi->dwMask & DBIM_TITLE) { lstrcpyW(pdbi->wszTitle, L"览栏例?); } if(pdbi->dwMask & DBIM_MODEFLAGS) { pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT; } if(pdbi->dwMask & DBIM_BKCOLOR) { //通过Udq个标志来用默认的背景颜色 pdbi->dwMask &= ~DBIM_BKCOLOR; } return S_OK; } return E_INVALIDARG; } ׃个接口的实现是可选择的,一个是IInputObjectQ另一个是 IContextMenu。本文的览栏例子实CIInputObject。对于IContextMenu的实现细节请参考有x档?br> IInputObject接口 如果某个band对象要接受用戯入。那必d现IInputObject接口。IE实现IInputObjectSiteq用IInputObjectl护用户的输入焦炏V浏览栏需要实C个方法:UIActivateIO, HasFocusIO, ?TranslateAcceleratorIO?br>IE调用UIActivateIO通知览栏它以被ȀzL者被|灰。当被激zLQ浏览栏例子调用SetFocus来设|窗口输入焦炏V?br>当要定哪个H口有输入焦Ҏ(gu)QIE调用HasFocusIO。如果浏览栏的窗口或它的子窗口之一有输入焦点,HasFocusIOq回S_OK。否则,它返回S_FALSE?br>TranslateAcceleratorIO允许对象处理键盘加速键。本文浏览栏例子没有实现q个Ҏ(gu)Q所以它q回S_FALSE?br>览栏例子实现IInputObjectSite的细节如下: 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; }H口q程 因ؓ区对象的昄用的是子H口Q所以它必须实现H口q程来处理Windows消息。浏览栏例子实现了一个最单的版本Q它的窗口过E只处理了五个消息:WM_NCCREATE, WM_PAINT, WM_COMMAND, WM_SETFOCUS, ?WM_KILLFOCUS。如果要实现更多的功能,很容易扩充它处理其它的消息?br> 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->lpCreateParams); SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis); //讄H口句柄 pThis->m_hWnd = hWnd; } break; case WM_PAINT: return pThis->OnPaint(); case WM_COMMAND: return pThis->OnCommand(wParam, lParam); case WM_SETFOCUS: return pThis->OnSetFocus(); case WM_KILLFOCUS: return pThis->OnKillFocus(); } return DefWindowProc(hWnd, uMessage, wParam, lParam); }q里WM_COMMAND消息处理器简单地q回零。WM_PAINT消息处理器创建文本ƈ昄在资源管理器或IE的区对象中?br> LRESULT CExplorerBar::OnPaint(void) { PAINTSTRUCT ps; RECT rc; BeginPaint(m_hWnd, &ps); GetClientRect(m_hWnd, &rc); SetTextColor(ps.hdc, RGB(255, 255, 255)); SetBkMode(ps.hdc, TRANSPARENT); DrawText(ps.hdc, TEXT("览栏例?), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(m_hWnd, &ps); return 0; }WM_SETFOCUS ?WM_KILLFOCUS消息处理器通过调用本现场的IInputObjectSite::OnFocusChangeISҎ(gu)通知输入焦点现场改变Q?br> 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->OnFocusChangeIS((IDockingWindow*)this, bFocus); } }四、ȝ 区对象提供了灉|和强大的扩展方式Q通过定制览栏得IE的功能大为增强。桌面区的实现扩展了普通窗口的能力。尽需要一些对COM的编E,但终I以子窗口的形式提供了一U用L面。从而今后的许多这U编E实现都能用cM的Windows~程技术。虽然本文所讨论的例子只提供了有限的功能Q但它示范了区对象全部的Ҏ(gu),q且可以在此基础上进行扩充来创徏独特和功能强大的的用L面? |
下蝲源代?/font> Band 样式 lgcd CATID
关键?/strong>QBandQDesk BandQExplorer BandQTool BandQ浏览器栏,工具栏,桌面工具?br>
一、引a
最q,׃工作的要求,我需要在 IE 上做一些开发工作。于是在 MSDN 上翻阅了一些资料,Ҏ(gu) MSDN 上的说明我用 ATL 胜利完成?#8220;资本家老板”分配的Q务?br>Qƈ且在白天睡觉的过E中梦到了老板l我加工资啦......Q?br>现在Q我?MSDN 上的原文资料Q经q翻译整理ƈ把一?ATL 的实现奉贤给 VCKBASE 上的朋友们?/p>
基本band 对象
必须实现?COM 接口
IPersistStream
IObjectWithSite
IDeskBand、IDockingWindow、IOleWindow
选择实现?COM 接口
Band 对象注册
在翻译的q程中,有两个词汇非怸好理解。第一个词?Band 对象Q词怸译?#8220;镶边、裙子边、带子、乐?.....”我的英文水^有限Q实在不知道应该译Z么词汇更合适。于是我毅然决然地决定:在如下的中,依然使用 band q个词!Q什么?没听明白Q我的意思就是说Q我不翻译这个词了)但到?Band 对象应该如何理解那?L图一Q?br>
图一
图一中画U圈的地方,分别UC“垂直的浏览器?#8221;?#8220;水^的浏览器?#8221;?#8220;工具?#8221;?#8220;桌面工具?#8221;。这?#8220;?#8221;Q都可以?IE ?#8220;查看”菜单中或鼠标右键的上下文快捷方式菜单中显C或隐藏h。这些界面窗口的实现Q其实就是实CU?COM 接口对象Q而这个对象叫 band。这个概念实在是只能意会而无法言传的Q我M能在文章中把它翻译ؓ“L靠在 IE ȝ口边上的对象”吧?^_^
另外Q还有一个词?site。这个很好翻译,?#8220;站点”Q。呵呵,我敢打包,如果你要能理解这个翻译在计算机类文章中的含义Q那只能恭喜你了,你的智慧太高了。(都是学计机软g的hQ做人的差距咋就q么大呢Q)在本文章中Qsite 可以q样理解QIE 的主框架四周Q就好比?#8220;汽R?#8221;Q那?band 对象Q就好比?#8220;汽R”。band 汽RL可以停靠?#8220;汽R?#8221;上。所以,site 是“站点”Q它也是 COM 接口的对象(IObjectWithSite、IInputObjectSiteQ?br>
3.1 基本 band 对象
Band 对象Q从 Shell 4.71(IE 5.0) 开始提供支持。Band 是一?COM 对象Q必L在一个容器中M用,当然使用它们好象用普通窗口是一L。IE 是一个容器,桌面 Shell 也是一个容器,它们提供不同的函数功能,但基本的实现是相似的?br> Band 对象分三U类型,览器栏 bandQExplorer bandsQ、工h bandQTool BandsQ和桌面工具?Desk bands)Q而浏览器?band 又有两种表现形式Q垂直和水^的。那?IE ?Shell 如何区分q加载这?bands 对象呢?Ҏ(gu)是:你要对不同的 band 对象Q在注册表中注册不同的组件类型(CATIDQ?br>
垂直的浏览器?/td>
CATID_InfoBand
00021493-0000-0000-C000-000000000046
水^的浏览器?/td>
CATID_CommBand
00021494-0000-0000-C000-000000000046
桌面的工h
CATID_DeskBand
00021492-0000-0000-C000-000000000046
IE 工具栏不使用lgcd注册Q而是使用在注册进?CLSID 的登记方式。详l情况见 3.3?br> 在例子程序中Q实C全部四个cd?band 对象Q垂直浏览器?CVerticalBar)昄了一?HTML 文gQƈ且实C?IE ȝ口浏览网늚D{功能;水^的浏览器?CHorizontalBar)是一个编辑窗Q它同步昄当前|页?BODY 源文件内容;IE 工具?CToolBar)最单,只是d了一个空的工hQ桌面工h(CDeskBar)实现了一个单行编辑窗口,你可以在上面输入命o行或文g名称Q回车后它会执行 Shell 的打开动作?br>
3.2 必须实现?COM 接口
Band 对象?IE ?Shell 的进E内服务器,所以它被包装在 DLL 中。而作?COM 对象Q它必须要实?IUnknown ?IClassFactory 接口。(大家可以不同操心Q因为我们用 ATL 写程序,q两个接口是不用我们自己写代码的。)另外QBand 对象q必d?IDeskBand、IObjectWithSite ?IPersistStream 三个接口Q?br> IPersistStream 是持l性接口的一U。当 IE 加蝲 band 对象的时候,它通过q个接口?Load Ҏ(gu)传递属性值给对象Q让其进行初始化Q而当卸蝲前,IE 则调用这个接口的 Save Ҏ(gu)保存对象的属性。用 ATL 实现q个接口很简单:
class ATL_NO_VTABLE Cxxx : ...... public IPersistStreamInitImpl, // dl承 ......{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)...... // d需要持l性的属性END_PROP_MAP()上面的代码,其实实现的是 IPersistStreamInit 接口Q不q没有关p,因ؓ IPersistStreamInit z?IPersistStreamQ实例化了派生类Q自然就实例化了基类。在例子E序中,我只在桌面工h对象中添加了持箋性属性,用来保存和初始化“命o?#8221;。另?COM_INTERFACE_ENTRY2(AQB)表示的含义是Q如果想查询A接口的指针,则提供B接口指针来代ѝؓ什么可以这样那Q因为B接口z自A接口Q那么B接口的前几个函数必然是A接口的函CQ自然B接口的地址其实和A接口的地址是一L了?br> IObjectWithSite ?IE 用来Ҏ(gu)件进行管理和通讯用的一个接口。必要实现q个接口?个函敎ͼSetSite() ?GetSite()。当 IE 加蝲 band 对象和释?band 对象的时候,都要调用 SetSite()函数Q那么在q个函数里正好是写初始化和释放操作代码的地方Q?
STDMETHODIMP Cxxx::SetSite(IUnknown *pUnkSite){ if( NULL == pUnkSite ) // 释放 band 的时?{ // 如果加蝲的时候,保存了一些接? // 那么现在Q释攑֮ } else // 加蝲 band 的时?{ m_hwndParent = NULL; // 装蝲 band 的父H口(是带有标题的那个框架窗? // q个H口的句柄,是调?IUnknown::QueryInterface() 得到 IOleWindow // 然后调用 IOleWindow::GetWindow() 而获得的? CComQIPtr< IOleWindow, &IID_IOleWindow > spOleWindow(pUnkSite); if( spOleWindow ) spOleWindow->GetWindow(&m_hwndParent); if( !m_hwndParent ) return E_FAIL; // 现在Q正好是建立子窗口的时机? // 注意Q子H口建立的时候,不要使用 WS_VISIBLE 属? ... ... // 在例子程序中Q用 CAxWindow 实现了一个能包容ActiveX的容器窗?垂直览器栏) // 在例子程序中Q用 WIN API 函数 CreateWindow 实现了标准窗?水^览器栏、工h) // 在例子程序中Q用 CWindowImpl 实现了一个包容窗?桌面工具? /*********************************************************/ 以下部分Q根?band 对象Ҏ(gu)的功能,是可以选择实现? **********************************************************/ // 如果子窗口实C用户输入Q那么必d?IInputObject 接口Q? // 而该接口是被 IE ?IInputObjectSite 调用的,因此在你的对? // 中,应该保存 IInputObjectSite 的接口指针? // 在类的头文g中,定义Q? // CComQIPtr< IInputObjectSite, &IID_IInputObjectSite > m_spSite; m_spSite = pUnkSite; // 保存 IInputObjectSite 指针 if( !m_spSite ) return E_FAIL; // 你需要控?IE 的主框架吗? // 那么在类的头文g中,定义Q? // CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_spFrameWB; // 然后Q先取得 IServiceProvider,再取?IWebBrowser2 CComQIPtr < IServiceProvider, &IID_IServiceProvider> spSP(pUnkSite); if( !spSP ) return E_FAIL; spSP->QueryService( SID_SWebBrowserApp, &m_spFrameWB ); if( !m_spFrameWB) return E_FAIL; // 如果你取得了 IE L架的 IWebBrowser2 指针 // 那么Q当它发生了什么事情,你难道不想知道吗Q? // 定义QCComPtr m_spCP; CComQIPtr< IConnectionPointContainer, &IID_IConnectionPointContainer> spCPC( m_spFrameWB ); if( spCPC ) { spCPC->FindConnectionPoint( DIID_DWebBrowserEvents2, &m_spCP ); if( m_spCP ) { m_spCP->Advise( reinterpret_cast< IDispatch * >( this ), &m_dwCookie ); } } // 咳~~~ 不说了,看源码去吧。这里能q的事情太多?.. ... } return S_OK;}IDeskBand 是一个特D的 band 对象接口Q有一个方法函敎ͼGetBarInfo()Q?br>IDockingWindow ?IDeskBank 的基c,?个方法函敎ͼShowDW()、CloseDW()、ResizeBorderDW()Q?br>IOleWindow 又是 IDockingWindow 的基c,?个方法函敎ͼGetWindow()、ContextSensitiveHelp()Q?
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 H口 if( m_hWnd ) ::ShowWindow( m_hWnd, bShow ? SW_SHOW : SW_HIDE); return S_OK;}STDMETHODIMP CVerticalBar::CloseDW(DWORD dwReserved){ // 销?band H口 if( ::IsWindow( m_hWnd ) ) ::DestroyWindow( m_hWnd ); m_hWnd = NULL; return S_OK;}STDMETHODIMP CVerticalBar::ResizeBorderDW(LPCRECT prcBorder, IUnknown* punkToolbarSite, BOOL fReserved){ // 当框架窗口的Ҏ(gu)大小改变?return E_NOTIMPL;}// IDeskBandSTDMETHODIMP CVerticalBar::GetBandInfo(DWORD dwBandID, DWORD dwViewMode, DESKBANDINFO* pdbi){ // 取得 band 的基本信息,你需要填?pdbi 参数作ؓq回 if( NULL == pdbi ) return E_INVALIDARG; // 如果来需要调?IOleCommandTarget::Exec() 则需要保存这2个参?m_dwBandID = dwBandID; m_dwViewMode = dwViewMode; if(pdbi->dwMask & DBIM_MINSIZE) { // 最尺? pdbi->ptMinSize.x = 10; pdbi->ptMinSize.y = 10; } if(pdbi->dwMask & DBIM_MAXSIZE) { // 最大尺?(-1 表示 4G) pdbi->ptMaxSize.x = -1; pdbi->ptMaxSize.y = -1; } if(pdbi->dwMask & DBIM_INTEGRAL) { pdbi->ptIntegral.x = 1; pdbi->ptIntegral.y = 1; } if(pdbi->dwMask & DBIM_ACTUAL) { pdbi->ptActual.x = 0; pdbi->ptActual.y = 0; } if(pdbi->dwMask & DBIM_TITLE) { // H口标题 wcscpy(pdbi->wszTitle,L"H口标题"); } if(pdbi->dwMask & DBIM_MODEFLAGS) { pdbi->dwModeFlags = DBIMF_VARIABLEHEIGHT; } if(pdbi->dwMask & DBIM_BKCOLOR) { // 如果使用默认的背景色Q则U除该标? pdbi->dwMask &= ~DBIM_BKCOLOR; } return S_OK;}3.3 选择实现?COM 接口
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;}Band 对象能够通过包容器的 IOleCommandTarget::Exec() 调用执行命o。?IOleCommandTarget 接口指针Q则可以通过调用包容器的 IInputOjbectSite::QueryInterfaceQIID_IOleCommandTarget,...Q?函数得到。CGID_DeskBand 是命令组Q当一?band 对象?GetBandInfo 被调用的时候,包容器通过 dwBandID 参数指定一?ID l?band 对象Q对象要保存住这个IDQ以便调?IOleCommandTarget::Exec()的时候用。ID 的命令有Q?
?/font> | 描述 |
---|---|
pUnk | band 对象?IUnknown 指针Q其它的桌面 bands 被隐藏 |
0 | 隐藏所有的桌面 bands |
1 | 昄所有的桌面 bands |
3.4 Band 对象注册
Band 对象必须注册Z?OLE q程内的服务器,q且支持 apartment U程公寓。注册表中默认键的值是表示菜单的文字。对于浏览器栏,它加?IE 菜单?#8220;查看\览器栏”中;对于工具?band Q它加到 IE 菜单?#8220;查看\工具?#8221;中;对于桌面 bandQ?它加到系lQ务栏的快捯单中。在菜单资源中,可以使用“&”指明加速键?br>
通常Q一个基本的 band 对象的注册表目是:
HKEY_CLASSES_ROOT
CLSID
{你的 band 对象?CLSID}
(Default) = 菜单的文?
InProcServer32
(Default) = DLL 的全路径文g?
ThreadingModel= Apartment
工具?bands q必L它们?CLSID 注册?IE 的注册表中?br>
?HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Toolbar 下给?CLSID 作ؓ键名Q而其键值是被忽略的?br>
HKEY_LOCAL_MACHINE
Software
Microsoft
Internet Explorer
Toolbar
{你的 band 对象?CLSID}
q有几个可选的注册表项?例子E序q不是这样实现的)。比如,你想让浏览器栏显C?HTML 的话Q必要如下讄注册表:
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
同时Q如果要指定一个本地的 HTML 文gQ那么要如下讄Q?
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
Instance
InitPropertyBag
Url
另外Q还可以指定览器栏的宽和高Q当Ӟ它是依赖于这个栏是纵向还是横向的。其实这个项目无所谓,因ؓ当用戯整了览器栏的大后Q会自动保存在注册表中的?br>
HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象?CLSID}
BarSize
BarSize 键的cd必须?REG_BINARY cdQ它?个字节。左起前4个字节,是用16q制表示的像素宽度或高度Q后4个字节保留,你应该设|ؓ0。下面是一个可以在览器栏上显C?HTML 文g的全部注册表目的例子,默认宽度?91Q?x123Q个像素点:
HKEY_CLASSES_ROOT
CLSID
{你的 Band 对象?CLSID}
(Default) = 菜单文字
InProcServer32
(Default) = DLL 的全路径文g?
ThreadingModel= Apartment
Instance
CLSID
(Default) = {4D5C8C2A-D075-11D0-B416-00C04FB90376}
InitPropertyBag
Url= 你的 HTML 文g?br>
HKEY_CURRENT_USER
Software
Microsoft
Internet Explorer
Explorer Bars
{你的 Band 对象?CLSID}
BarSize= 23 01 00 00 00 00 00 00
对于注册表的讄Q用 ATL 实现其实是异常简单的。打开工程?xxx.rgs 文gQƈ手工~辑一下就可以了?下面q个文g源码Q是例子E序?IE 工具栏的注册表样式,HKLM 是需要手工添加的Q因为它不用组件类型方式注册。而对于其它类型的 band 对象只要在类声明中添加:
BEGIN_CATEGORY_MAP(Cxxx) // 向注册表中注?COM cd IMPLEMENTED_CATEGORY(CATID_InfoBand) // 垂直样式的浏览器栏END_CATEGORY_MAP()IE 工具栏类?band 对象?#8220;.rgs”文g
HKCR // q个目?ATL 帮你生成的,你只要手工修?#8220;菜单上的文字”可以了{ 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 ''用在菜单上的文字(&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 // q个目是手工添加的IE工具栏所Ҏ(gu)的{ Software { Microsoft { ''Internet Explorer'' { NoRemove Toolbar { ForceRemove val { 你的 CLSID } = s ''随便l个说明性文字串'' } } } }}四?ATL 实现
下蝲源代?/font>
一、前a
我们写程序,l常需要实现这L需求:
例一、程序运行生一个窗口,用户关闭的时候需要记录窗口的位置Q以便下ơ运行时保持位置不变Q?br>例二、由于程序运行时间很长,今天执行一部分Q明天l执行。那么在下次q行前要恢复前次的状态;
... ... ... ...
智慧的老师Q以上这些需求,如何实现呢?
應|的学生:q个单,只要在程序退出前提取必要的信息保存到文g中,下次q行时再从文件中d来,讄一下就O(jin)K了?br>智慧的老师Q恩Q不错,q位同学的思想值得表扬?br>應|的学生:不好意思,q都要感谢老师的栽培,我对(zhn)的景Ԓ如滔滔江?.....
智慧的老师Q别臭P了,我话q没有说完那......如果你需要提取和保存的信息很多,l构很复?.....怎么办?
應|的学生:也好办,我设计一个结构来记录q些信息?br>智慧的老师Q恩......不错。但如果q些信息提供Ҏ(gu)别h写的模块Qƈ且随着版本的不同还l常变化Q你怎么办?
應|的学生:... ...
智慧的老师Q解册些问题的Ҏ(gu)?--持箋性?strong>
二、原?br>
持箋性接?/strong> | 要说?/strong> | |
IPersist | 所有持l性接口的根,下面的接口大多从它派生出来。这个接口很单,只有一个函?GetClassID()它返回组件的 CLSID P以便调用者能保存q个号ؓ来 CoCreateInstance() 启动lg用?br> 实现q个函数也很单,只要q回你组件中?CLSID_XXX 卛_Q或者比较省事的Ҏ(gu)是返?GetObjectCLSID() ?/td> | |
IPersistStream |
z?IPersistQƈ增加?个函敎ͼ从流(IStream)中读写组件属性信息?/p> |
|
IsDirty() | lg内部属性是否发生了变化。ؓ调用者是否需要保存信息提供依?/td> | |
Load() | ?IStream 中读入信息,初始化组件属?/td> | |
Save() | 把属性信息保存到 IStream ?/td> | |
GetSizeMax() | q回信息寸Q以便调用者事先开辟空?/td> | |
IPersistStreamInit | z?IPersistStreamQƈ再增加了一个函?InitNew() 用来完成一个默认的lg属性初始化?br>q个持箋性接口是最常用的,本文CZ中就实现了该接口?/td> | |
IPersistMemory | ?IPersistStreamInit cMQ但使用的是内存块,而不是大可变化?IStream ?/td> | |
IPersistStorage | ?IPersistStream cMQ但保存属性信息用的是存?IStorageQ一?IStorage 中可以有多个 IStream?/td> | |
IPersistFile | ?IPersistStream cMQ但存储介质为文件?/td> | |
IPersistPropertyBag | 使用属性包Q属性名、属性|的文本方式保存信息。在 IE 览器中QHTML 嵌入 ActiveX 控g通常使用q个Ҏ(gu)?br> ?HTML 中插入控Ӟ<param name="属性名U? value="?> q样的Ş式你应该见过吧?Q?br> 在下一回的文章中,我们介绍q个接口。因为在 ActiveX 中,它太常用了?/td> | |
IPersistPropertyBag2 | 扩展?IPersistPropertyBag 接口。提供了更丰富一些的属性管理用函数?/td> | |
IPersistMoniker | 用于命名(moniker)存储和读取状态的持箋性接口?/td> | |
IPersistHistory | q行?IE 上,惛_用户览 WEB 面时存储和d状态的持箋性接口?/td> |
三、持l性接口组件的实现
CZE序分别?vc6.0 ?vc.net 上实C IPersistStreamInit 接口?COM lg和调用D例。组件完成的功能是计素敎ͼ你第一ơ运行的时候,会得到第一个素?Q然后是3Q?Q?Q?1......下班旉CQ今天就q行到这里。于是调用者开辟一个流来保存组件的属性信息。明天l运行的时候,从流中原换组件状态,开始了新的计算 13Q?7Q?9Q?3......
q个CZ应用完全是假设性的Q其实没有什么实用h(hun)|只是演示?IPersistStreamInit 接口的实现方法。另外,关于建立?IStream)的方法,请参?a >?/font>COM lg设计与应用(一Q?/font>?br>
1、徏立一?ATL 工程目?br>2、增?ATL lgc,vc.net 使用者注意不要选择“属性化~程”方式Q其它的讄全部使用默认Ҏ(gu)。当然你愿意适当地改变选择也无所谓?br>3、设计完成你的组件功能?br> CZE序中,实现了一个接口函?GetNext() 负责计算下一个素数?br>4、添加IPersistStreamInit 接口?/p>
class ATL_NO_VTABLE Cxxx : public CComObjectRootEx<...> , public CComCoClass<...>, ...... public IPersistStreamInit // 手工d持箋性接?/strong>{......BEGIN_COM_MAP(Cxxx) ...... // 手工d接口映射表入?COM_INTERFACE_ENTRY(IPersistStreamInit) // 表示如果要取?IPersistStream 指针Q则q回 IPersistStreamInit 指针 COM_INTERFACE_ENTRY_IID(IID_IPersistStream, IPersistStreamInit) // 表示如果要取?IPersist 指针Q则q回 IPersistStremInit 指针 COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersistStreamInit)END_COM_MAP()5、完?IPersistStreamInit 接口函数?br>手工?h 头文件中增加函数声明Q?
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);手工?cpp 文g中增加函数实玎ͼ
// IPersistSTDMETHODIMP Cxxx::GetClassID(/*[out]*/CLSID * pClassID){ *pClassID = GetObjectCLSID(); return S_OK;}// IPersistStreamSTDMETHODIMP Cxxx::IsDirty(void){ if( 数据已经改变Q需要保?) return S_OK; else return S_FALSE;}STDMETHODIMP Cxxx::Load(/*[in]*/IStream *pStm){ return pStm->Read( d哪里, d长字? NULL);}STDMETHODIMP Cxxx::Save(/*[in]*/IStream *pStm,/*[in]*/BOOL fClearDirty){ if( fClearDirty ) 清除内部表示数据变化的变? return pStm->Write( 需要保存的数据指针, 写多长字? NULL );}STDMETHODIMP Cxxx::GetSizeMax(/*[out]*/ULARGE_INTEGER *pcbSize){ pcbSize->LowPart = 需要保存数据长度的低位; pcbSize->HighPart = 需要保存数据长度的高位;// 一般都?Q难道你的数据长度都过?4GQ?return S_OK;}// IPersistStreamInitSTDMETHODIMP Cxxx::InitNew(void){ 内部属性数据默认初始化; 讄或清除内部表C数据变化的变量; return S_OK;}四、小l?/strong>