一、复合文件的特点
|
|
- 复合文件的内部是使用指针构造的一棵树进行管理的。编写程序的时候要注意,由于使用的是单向指针,因此当做定位操作的时候,向后定位比向前定位要快;
- 复合文件中的“流对象”,是真正保存数据的空间。它的存储单位为512字节。也就是说,即使你在流中只保存了一个字节的数据,它也要占据512字节的文件空间。
- 不同的进程,或同一个进程的不同线程可以同时访问一个复合文件的不同部分而互不干扰;
- 大家都有这样的体会,当需要往一个文件中插入一个字节的话,需要对整个文件进行操作,非常烦琐并且效率低下。而复合文件则提供了非常方便的“增量访问”能力;
- 当频繁地删除文件,复制文件后,磁盘空间会变的很零碎,需要使用磁盘整理工具进行重新整合。和磁盘管理非常相似,复合文件也会产生这个问题,在适当的时候也需要整理,但比较简单,只要调用一个函数就可以完成了。
|
|
示例一:建立一个复合文件,并在其下建立一个子存储,在该子存储中再建立一个流,写入数据。 |
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 // ANSI、MBCS、UNICODE 转换
void SampleEnum()
{ // 假设你已经做过 COM 初始化了
LPCTSTR lpFileName = _T( "c:\\a.stg" );
HRESULT hr;
IStorage *pStg = NULL;
USES_CONVERSION;
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 ); // 释放名称所使用的内存
}
if( pEnum ) pEnum->Release();
if( pStg ) pStg->Release();
}
|
|
二、持续性原理 |
持续性,也叫永久性。组件方提供 IPersistXXX 接口,调用者(容器)提供存储介质,比如文件啦、内存啦、注册表啦、流啦、文本啦......啦啦拉。需要保存的时候,调用者通过 IPersistXXX::Save() 接口函数让组件去自己存储属性信息,而调用者根本不用关心存储格式和存储内容;需要还原状态的时候,调用者打开存储介质,然后同样调用 IPersistXXX::Load() 接口函数让组件自己去读取属性信息并完成初始化的设置。 |
|
持续性接口组件的实现 |
1、建立一个 ATL 工程项目。 2、增加 ATL 组件类,vc.net 使用者注意不要选择“属性化编程”方式,其它的设置全部使用默认方法。当然你愿意适当地改变选择也无所谓。 3、设计完成你的组件功能。 示例程序中,实现了一个接口函数 GetNext() 负责计算下一个素数。 4、添加IPersistStreamInit 接口。 |
|
class ATL_NO_VTABLE Cxxx :
public CComObjectRootEx<...>,
public CComCoClass<...>,
......
public IPersistStreamInit // 手工添加持续性接口
{
......
BEGIN_COM_MAP(Cxxx)
......
// 手工添加接口映射表入口
COM_INTERFACE_ENTRY(IPersistStreamInit)
// 表示如果要取得 IPersistStream 指针,则返回 IPersistStreamInit 指针
COM_INTERFACE_ENTRY_IID(IID_IPersistStream, IPersistStreamInit)
// 表示如果要取得 IPersist 指针,则返回 IPersistStremInit 指针
COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersistStreamInit)
END_COM_MAP()
|
|
5、完成 IPersistStreamInit 接口函数。 手工在 h 头文件中增加函数声明: |
|
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 文件中增加函数实现: |
|
// IPersist
STDMETHODIMP Cxxx::GetClassID(/*[out]*/CLSID * pClassID)
{
*pClassID = GetObjectCLSID();
return S_OK;
}
// IPersistStream
STDMETHODIMP Cxxx::IsDirty(void)
{
if( 数据已经改变,需要保存 ) return S_OK;
else return S_FALSE;
}
STDMETHODIMP Cxxx::Load(/*[in]*/IStream *pStm)
{
return pStm->Read( 读到哪里, 读多长字节, 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 = 需要保存数据长度的高位;// 一般都是0,难道你的数据长度都超过了 4G?
return S_OK;
}
// IPersistStreamInit
STDMETHODIMP Cxxx::InitNew(void)
{
内部属性数据默认初始化;
设置或清除内部表示数据变化的变量;
return S_OK;
}
|
|
示例代码:http://xmyang./inc/PersistSample.rar |