一、復(fù)合文件的特點(diǎn)
|
|
- 復(fù)合文件的內(nèi)部是使用指針構(gòu)造的一棵樹進(jìn)行管理的。編寫程序的時候要注意,由于使用的是單向指針,因此當(dāng)做定位操作的時候,向后定位比向前定位要快;
- 復(fù)合文件中的“流對象”,是真正保存數(shù)據(jù)的空間。它的存儲單位為512字節(jié)。也就是說,即使你在流中只保存了一個字節(jié)的數(shù)據(jù),它也要占據(jù)512字節(jié)的文件空間。
- 不同的進(jìn)程,或同一個進(jìn)程的不同線程可以同時訪問一個復(fù)合文件的不同部分而互不干擾;
- 大家都有這樣的體會,當(dāng)需要往一個文件中插入一個字節(jié)的話,需要對整個文件進(jìn)行操作,非常煩瑣并且效率低下。而復(fù)合文件則提供了非常方便的“增量訪問”能力;
- 當(dāng)頻繁地刪除文件,復(fù)制文件后,磁盤空間會變的很零碎,需要使用磁盤整理工具進(jìn)行重新整合。和磁盤管理非常相似,復(fù)合文件也會產(chǎn)生這個問題,在適當(dāng)?shù)臅r候也需要整理,但比較簡單,只要調(diào)用一個函數(shù)就可以完成了。
|
|
示例一:建立一個復(fù)合文件,并在其下建立一個子存儲,在該子存儲中再建立一個流,寫入數(shù)據(jù)。 |
void SampleCreateDoc()
{
::CoInitialize(NULL); // COM 初始化
// 如果是MFC程序,可以使用AfxOleInit()替代
HRESULT hr; // 函數(shù)執(zhí)行返回值
IStorage *pStg = NULL; // 根存儲接口指針
IStorage *pSub = NULL; // 子存儲接口指針
IStream *pStm = NULL; // 流接口指針
hr = ::StgCreateDocfile( // 建立復(fù)合文件
L"c:\\a.stg", // 文件名稱
STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, // 打開方式
0, // 保留參數(shù)
&pStg); // 取得根存儲接口指針
ASSERT( SUCCEEDED(hr) ); // 為了突出重點(diǎn),簡化程序結(jié)構(gòu),所以使用了斷言。
// 在實(shí)際的程序中則要使用條件判斷和異常處理
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( // 向流中寫入數(shù)據(jù)
"Hello", // 數(shù)據(jù)地址
5, // 字節(jié)長度(注意,沒有寫入字符串結(jié)尾的\0)
NULL); // 不需要得到實(shí)際寫入的字節(jié)長度
ASSERT( SUCCEEDED(hr) );
if( pStm ) pStm->Release();// 釋放流指針
if( pSub ) pSub->Release();// 釋放子存儲指針
if( pStg ) pStg->Release();// 釋放根存儲指針
::CoUninitialize() // COM 釋放
// 如果使用 AfxOleInit(),則不調(diào)用該函數(shù)
}
|
|
示例二:打開一個復(fù)合文件,枚舉其根存儲下的所有對象。 |
#include // ANSI、MBCS、UNICODE 轉(zhuǎn)換
void SampleEnum()
{ // 假設(shè)你已經(jīng)做過 COM 初始化了
LPCTSTR lpFileName = _T( "c:\\a.stg" );
HRESULT hr;
IStorage *pStg = NULL;
USES_CONVERSION;
LPCOLESTR lpwFileName = T2COLE( lpFileName ); // 轉(zhuǎn)換T類型為寬字符
hr = ::StgIsStorageFile( lpwFileName ); // 是復(fù)合文件嗎?
if( FAILED(hr) ) return;
hr = ::StgOpenStorage( // 打開復(fù)合文件
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 ); // 釋放名稱所使用的內(nèi)存
}
if( pEnum ) pEnum->Release();
if( pStg ) pStg->Release();
}
|
|
二、持續(xù)性原理 |
持續(xù)性,也叫永久性。組件方提供 IPersistXXX 接口,調(diào)用者(容器)提供存儲介質(zhì),比如文件啦、內(nèi)存啦、注冊表啦、流啦、文本啦......啦啦拉。需要保存的時候,調(diào)用者通過 IPersistXXX::Save() 接口函數(shù)讓組件去自己存儲屬性信息,而調(diào)用者根本不用關(guān)心存儲格式和存儲內(nèi)容;需要還原狀態(tài)的時候,調(diào)用者打開存儲介質(zhì),然后同樣調(diào)用 IPersistXXX::Load() 接口函數(shù)讓組件自己去讀取屬性信息并完成初始化的設(shè)置。 |
|
持續(xù)性接口組件的實(shí)現(xiàn) |
1、建立一個 ATL 工程項(xiàng)目。 2、增加 ATL 組件類,vc.net 使用者注意不要選擇“屬性化編程”方式,其它的設(shè)置全部使用默認(rèn)方法。當(dāng)然你愿意適當(dāng)?shù)馗淖冞x擇也無所謂。 3、設(shè)計完成你的組件功能。 示例程序中,實(shí)現(xiàn)了一個接口函數(shù) GetNext() 負(fù)責(zé)計算下一個素數(shù)。 4、添加IPersistStreamInit 接口。 |
|
class ATL_NO_VTABLE Cxxx :
public CComObjectRootEx<...>,
public CComCoClass<...>,
......
public IPersistStreamInit // 手工添加持續(xù)性接口
{
......
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 接口函數(shù)。 手工在 h 頭文件中增加函數(shù)聲明: |
|
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 文件中增加函數(shù)實(shí)現(xiàn): |
|
// IPersist
STDMETHODIMP Cxxx::GetClassID(/*[out]*/CLSID * pClassID)
{
*pClassID = GetObjectCLSID();
return S_OK;
}
// IPersistStream
STDMETHODIMP Cxxx::IsDirty(void)
{
if( 數(shù)據(jù)已經(jīng)改變,需要保存 ) return S_OK;
else return S_FALSE;
}
STDMETHODIMP Cxxx::Load(/*[in]*/IStream *pStm)
{
return pStm->Read( 讀到哪里, 讀多長字節(jié), NULL);
}
STDMETHODIMP Cxxx::Save(/*[in]*/IStream *pStm,/*[in]*/BOOL fClearDirty)
{
if( fClearDirty ) 清除內(nèi)部表示數(shù)據(jù)變化的變量;
return pStm->Write( 需要保存的數(shù)據(jù)指針, 寫多長字節(jié), NULL );
}
STDMETHODIMP Cxxx::GetSizeMax(/*[out]*/ULARGE_INTEGER *pcbSize)
{
pcbSize->LowPart = 需要保存數(shù)據(jù)長度的低位;
pcbSize->HighPart = 需要保存數(shù)據(jù)長度的高位;// 一般都是0,難道你的數(shù)據(jù)長度都超過了 4G?
return S_OK;
}
// IPersistStreamInit
STDMETHODIMP Cxxx::InitNew(void)
{
內(nèi)部屬性數(shù)據(jù)默認(rèn)初始化;
設(shè)置或清除內(nèi)部表示數(shù)據(jù)變化的變量;
return S_OK;
}
|
|
示例代碼:http://xmyang./inc/PersistSample.rar |