赞
踩
打开记事本程序,输入了一篇文章后,保存。——这样的文件叫“非结构化文件”;
打开电子表格程序,输入一个班的学生姓名和考试成绩,保存。——这样的文件叫“标准结构化文件”;
在我们写的程序中,需要把特定的数据按照一定的结构和顺序写到文件中保存。——这样的文件叫“自定义结构
化文件”;(比如 *.bmp 文件)
微软是作磁盘操作系统起家的,于是很自然地他们提出了一个非常完美的设计方案,那就是把磁盘文件
的管理方式移植到文件中了------复合文件,俗称“文件中的文件系统”。
复合文件是 COM 的基石。
三、复合文件的特点
1. 复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,由于使用的是单向指针,因此当做
定位操作的时候,向后定位比向前定位要快;
2. 复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即使你在流中只保存了一个
字节的数据,它也要占据512字节的文件空间。啊~~~,这也太浪费了呀?不浪费!因为文件保存在磁盘上,即使
一个字节也还要占用一个“簇”的空间那;
3. 不同的进程,或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰;
4. 大家都有这样的体会,当需要往一个文件中插入一个字节的话,需要对整个文件进行操作,非常烦琐并且效率低
下。而复合文件则提供了非常方便的“增量访问”能力;
5. 当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,需要使用磁盘整理工具进行重新整合。和磁盘管理非常
相似,复合文件也会产生这个问题,在适当的时候也需要整理,但比较简单,只要调用一个函数就可以完成了。
复合文件的函数和磁盘目录文件的操作非常类似.
所有这些函数,被分为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()调用中使用的内存句柄
示例一:建立一个复合文件,并在其下建立一个子存储,在该子存储中再建立一个流,写入数据。
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(),则不调用该函数
}
示例二:打开一个复合文件,枚举其根存储下的所有对象。
#include <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,
&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();
}
留作业啦......
作业1:写个小应用程序,从 MSWORD 的 doc 文件中,提取出附加信息(作者、公司......)。
作业2:写个全功能的“复合文件浏览编辑器”。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。