1、前言
公元一九九五年某个夜黑风高的晚上,个人一位老师跟我说:“小杨呀,之后写程序就和搭积木同样啦。你赶快学习一些OLE的技术吧......”,当时我内心就寻思 :“开什么玩笑?搭积木方式写程序?再过100年吧......”,但做为一名听话的好学生,我开始在书店里“踅摸”(注1)有关OLE的书籍(注2)。功夫不负有心人,终于买到了个人第一本COM书《OLE2 高级编程技术》,这本800多页的大布头花费了我1/5的月工资呀......因而开始日夜耕读.....
功夫不负有心人,我坚持读完了所有著做,感想是:这本书,在说什么呐?
功夫不负有心人,我又读完了一遍大布头,感想是:咳~~~,没懂!
功夫不负有心人,我再,我再,我再读 ... 感想是:哦~~~,读懂了一点点啦,哈哈哈。
...... ......
功夫不负有心人,我终于,我终于懂了。
800页的书对如今的我来讲,其实也就10几页有用。到这时候才体会出什么叫“书越读越薄”的道理了。到后来,能买到的书也多了,上网也更方便更便宜了......
为了让VCKBASE上的朋友,再也不经历我曾经的痛苦、再也不重蹈我“无头苍蝇”般探索的艰辛、为了VCKBASE的蓬勃发展、为了中国软件事业的腾飞(糟糕,吹的太也高了)......我打算节约一些在 BBS 上赚分的时间,写个系列论文,就叫“COM组件设计与应用”吧。今天是第一部分——起源。
2、文件的存储
传说350年前,牛顿被苹果砸到了头,因而发现了万有引力。但到了二十一世纪的如今,任何一个技术的发明和发展,已经再也不依靠圣人灵光的一闪。技术的进步转而是被社会的需求、商业的利益、竞争的压力、行业的渗透等推进的。微软在Windows平台上的组件技术也不例外,它的发明,有其必然因素。什么是这个因素那?答案是——文件的存储。
打开记事本程序,输入了一篇文章后,保存。——这样的文件叫“非结构化文件”;
打开电子表格程序,输入一个班的学生姓名和考试成绩,保存。——这样的文件叫“标准结构化文件”;
在咱们写的程序中,须要把特定的数据按照必定的结构和顺序写到文件中保存。——这样的文件叫“自定义结构化文件”;(好比 *.bmp 文件)
以上三种类型的文件,你们都见的多了。那么文件存储就依靠上述的方式能知足全部的应用需求吗?恩~~~,至少从计算机发明后的50多年来,一直是够用的了。嘿嘿,下面看看商业利益的推进做用,对文件 的存储形式产生了什么变化吧。30岁以上的朋友,我估计之前都使用过如下几个著名的软件:WordStar(独霸DOS下的英文编辑软件),WPS(裘伯君写的中文编辑软件,听说当年的市场占有率高达90%,各类计算机培训班的必修课程),LOTUS-123(莲花公司出品的电子表格软件)......
微软在成功地推出 Windows 3.1 后,开始垂涎桌面办公自动化软件领域。微软的 OFFICE 开发部门,各小组分别独立地开发了 WORD 和 EXCEL 等软件,并采用“自定义结构”方式,对文件进行存储。在激烈的市场竞争下,为了战胜竞争对手,微软天然地产生了一个念头------若是我能在 WORD 程序中嵌入 EXCEL,那么用户在购买了我 WORD 软件的状况下,不就没有必要再买 LOTUS-123 了吗?!“恶毒”(中国微软的同志们看到了这个词,不要激动,我是加了引号的呀)的计划产生后,他们开始了实施工做,这就是 COM 的前身 OLE 的起源(注3)。但马上就遇到了一个严重的技术问题:须要把 WORD 产生的 DOC 文件和 EXCEL 产生的 XLS 文件保存在一块儿。编程
方案浏览器
优势编辑器
缺点函数
创建一个子目录,把 DOC、XLS 存储在这同一个子目录中。
数据隔离性好,WORD 不用了解 EXCEL 的存储结构;容易扩展。
结构太松散,容易形成数据的损坏或丢失。
不易携带。工具
修改文件存储结构,在DOC结构基础上扩展出包容 XLS 的结构。
结构紧密,容易携带和统一管理。
WORD 的开发人员须要通晓 EXCEL 的存储格式;缺乏扩展性,总不能新加一个类型就扩展一下结构吧?!性能
以上两个方案,都有严重的缺陷,怎么解决那?若是能有一个新方案,可以合并前两个方案的优势,消灭缺点,该多好呀......微软是做磁盘操做系统起家的,因而很天然地他们提出了一个很是完美的设计方案,那就是把磁盘文件的管理方式移植到文件中了------复合文件,俗称“文件中的文件系统”。连微软当年都没有想到,就这么一个简单的想法,竟然最后就演变出了 COM 组件程序设计的方法。能够说,复合文件是 COM 的基石。下图是磁盘文件组织方式与复合文件组织方式的类比图:学习
图1、左侧表示一个磁盘下的文件组织方式,右侧表示一个复合文件内部的数据组织方式。
3、复合文件的特色
一、复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,因为使用的是单向指针,所以当作定位操做的时候,向后定位比向前定位要快;
二、复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即便你在流中只保存了一个字节的数据,它也要占据512字节的文件空间。啊~~~,这也太浪费了呀?不浪费!由于文件保存在磁盘上,即便一个字节也还要占用一个“簇”的空间那;
三、不一样的进程,或同一个进程的不一样线程能够同时访问一个复合文件的不一样部分而互不干扰;
四、你们都有这样的体会,当须要往一个文件中插入一个字节的话,须要对整个文件进行操做,很是烦琐而且效率低下。而复合文件则提供了很是方便的“增量访问”能力;
五、当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,须要使用磁盘整理工具进行从新整合。和磁盘管理很是类似,复合文件也会产生这个问题,在适当的时候也须要整理,但比较简单,只要调用一个函数就能够完成了。操作系统
4、浏览复合文件
VC6.0 附带了一个工具软件“复合文件浏览器”,文件名是“vc目录\Common\Tools\DFView.exe”。为了方便使用该程序,能够把它加到工具(tools)菜单中。方法是:Tools\Customize...\Tools卡片中增长新的项目。运行 DFView.exe,就能够打开一个复合文件进行观察了(注4)。但奇怪的是,在 Microsoft Visual Studio .NET 2003 中,我反而找不到这个工具程序了,汗!不过这刚好提供给你们一个练习的机会,在你阅读完本篇文章并掌握了编程方法后,本身写一个“复合文件浏览编辑器”程序,又练手了,还有实用的价值。
五、复合文件函数
复合文件的函数和磁盘目录文件的操做很是相似。全部这些函数,被分为3种类型:WIN API 全局函数,存储 IStorage 接口函数,流 IStream 接口函数。什么是接口?什么是接口函数?之后的文章中再陆续介绍,这里你们只要把“接口”当作是完成一组相关操做功能的函数集合就能够了。线程
WIN API 函数设计
功能说明
StgCreateDocfile()
创建一个复合文件,获得根存储对象
StgOpenStorage()
打开一个复合文件,获得根存储对象
StgIsStorageFile()
判断一个文件是不是复合文件
IStorage 函数
功能说明
CreateStorage()
在当前存储中创建新存储,获得子存储对象
CreateStream()
在当前存储中创建新流,获得流对象
OpenStorage()
打开子存储,获得子存储对象
OpenStream()
打开流,获得流对象
CopyTo()
复制存储下的全部对象到目标存储中,该函数能够实现“整理文件,释放碎片空间”的功能
MoveElementTo()
移动对象到目标存储中
DestoryElement()
删除对象
RenameElement()
重命名对象
EnumElements()
枚举当前存储中全部的对象
SetElementTimes()
修改对象的时间
SetClass()
在当前存储中创建一个特殊的流对象,用来保存CLSID(注5)
Stat()
取得当前存储中的系统信息
Release()
关闭存储对象
IStream 函数
功能说明
Read()
从流中读取数据
Write()
向流中写入数据
Seek()
定位读写位置
SetSize()
设置流尺寸。若是预先知道大小,那么先调用这个函数,能够提升性能
CopyTo()
复制流数据到另外一个流对象中
Stat()
取得当前流中的系统信息
Clone()
克隆一个流对象,方便程序中的不一样模块操做同一个流对象
Release()
关闭流对象
WIN API 补充函数
功能说明
WriteClassStg()
写CLSID到存储中,同IStorage::SetClass()
ReadClassStg()
读出WriteClassStg()写入的CLSID,至关于简化调用IStorage::Stat()
WriteClassStm()
写CLSID到流的开始位置
ReadClassStm()
读出WriteClassStm()写入的CLSID
WriteFmtUserTypeStg()
写入用户指定的剪贴板格式和名称到存储中
ReadFmtUserTypeStg()
读出WriteFmtUserTypeStg()写入的信息。方便应用程序快速判断是不是它须要的格式数据。
CreateStreamOnHGlobal()
内存句柄 HGLOBAL 转换为流对象
GetHGlobalFromStream()
取得CreateStreamOnHGlobal()调用中使用的内存句柄
为了让你们快速地浏览和掌握基本方法,上面所列表的函数并非所有,我省略了“事务”函数和未实现函数部分。更全面的介绍,请阅读 MSDN。
下面程序片断,演示了一些基本函数功能和调用方法。
示例一:创建一个复合文件,并在其下创建一个子存储,在该子存储中再创建一个流,写入数据。
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, // 保留参数
&pStg); // 取得根存储接口指针
ASSERT( SUCCEEDED(hr) ); // 为了突出重点,简化程序结构,因此使用了断言。
// 在实际的程序中则要使用条件判断和异常处理
hr = pStg->CreateStorage( // 创建子存储
L"SubStg", // 子存储名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pSub); // 取得子存储接口指针
ASSERT( SUCCEEDED(hr) );
hr = pSub->CreateStream( // 创建流
L"Stm", // 流名称
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,
0,0,
&pStm); // 取得流接口指针
ASSERT( SUCCEEDED(hr) );
hr = pStm->Write( // 向流中写入数据
"Hello", // 数据地址
5, // 字节长度(注意,没有写入字符串结尾的\0)
NULL); // 不须要获得实际写入的字节长度
ASSERT( SUCCEEDED(hr) );
if( pStm ) pStm->Release(); // 释放流指针
if( pSub ) pSub->Release(); // 释放子存储指针
if( pStg ) pStg->Release(); // 释放根存储指针
::CoUninitialize() // COM 释放
// 若是使用 AfxOleInit(),则不调用该函数
}
图2、运行示例程序一后,使用 DFView.exe 打开观察复合文件的效果图
示例二:打开一个复合文件,枚举其根存储下的全部对象。
#include // 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,
&pStg); // 获得根存储接口指针
IEnumSTATSTG *pEnum=NULL; // 枚举器
hr = pStg->EnumElements( 0, NULL, 0, &pEnum );
ASSERT( SUCCEEDED(hr) );
STATSTG statstg;
while( NOERROR == pEnum->Next( 1, &statstg, NULL) )
{
// statstg.type 保存着对象类型 STGTY_STREAM 或 STGTY_STORAGE
// statstg.pwcsName 保存着对象名称
// ...... 还有时间,长度等不少信息。请查看 MSDN
::CoTaskMemFree( statstg.pwcsName ); // 释放名称所使用的内存(注6)
}
if( pEnum ) pEnum->Release();
if( pStg ) pStg->Release();
}
6、小结 复合文件,结构化存储,是微软组件思想的起源,在此基础上继续发展出了持续性、命名、ActiveX、对象嵌入、现场激活......一系列的新技术、新概念。所以理解和掌握 复合文件是很是重要的,即便在你的程序中并无全面使用组件技术,复合文件技术也是能够单独被应用的。祝你们学习快乐,为社会主义软件事业而奋斗:-)留做业啦......做业1:写个小应用程序,从 MSWORD 的 doc 文件中,提取出附加信息(做者、公司......)。做业2:写个全功能的“复合文件浏览编辑器”。注1:踅摸(xuemo),动词,北方方言,寻找搜索的意思。注2:问:为何不上网查资料学习?答:开什么国际玩笑!在那遥远的1995年代,个人500块工资,不吃不喝正好够上100小时的Internet网。注3:OLE,对象的链接与嵌入。注4:能够用 DFView.exe 打开 MSWORD 的 DOC 文件进行复合文件的浏览。可是该程序并无实现国际化,不能打开中文文件名的复合文件,所以须要更名后才能浏览。注5:CLSID,在后续的文章中介绍。注6:关于 COM 中内存使用的问题,在后续的文章中介绍。