1. 原理:磁盘文件组织方式与复合文件组织方式:
2. 工具:VC自带的“复合文件浏览器”:DFView(没有实现国际化,文件全路径名不得出现中文)
3. 方法:
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()调用中使用的内存句柄 |
4. 源码1:写一个复合文件
::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(),则不调用该函数
5. 源码2:读一个复合文件
需要加入头文件:
#include <atlbase.h> #include <atlconv.h>
函数体:
::CoInitialize(NULL); // 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) ) { //我们从stststg结构体重查看流内容,具体的内容可以看MSDN ::CoTaskMemFree( statstg.pwcsName ); // 释放名称所使用的内存 } if( pEnum ) pEnum->Release(); if( pStg ) pStg->Release(); ::CoUninitialize() // COM 释放
6. 扩展
MsgEx.db为QQ聊天记录的内容,我们可以用DFView查看,但是是十六进制的,要经过解码