COM 組件設計與應用(十七)——持續性

http://www.vckbase.com/code/downcode.asp?id=2784 下載源碼

一、前言   我們寫程序,經常需要實現這樣的需求: 例一、程序運行產生一個窗口,用戶關閉的時候需要記錄窗口的位置,以便下次運行時保持位置不變; 例二、由於程序運行時間很長,今天執行一部分,明天繼續執行。那麼在下次運行前要恢復前次的狀態; ... ... ... ... 智慧的老師:以上這些需求,如何實現呢? 懵懂的學生:這個簡單,只要在程序退出前提取必要的信息保存到文件中,下次運行時再從文件中讀出來,設置一下就OK了。 智慧的老師:恩,不錯,這位同學的思想值得表揚。 懵懂的學生:不好意思,這都要感謝老師的栽培,我對您的景仰如滔滔江水...... 智慧的老師:別臭P了,我話還沒有說完那......如果你需要提取和保存的信息很多,結構很複雜......怎麼辦? 懵懂的學生:也好辦,我設計一個結構來記錄這些信息。 智慧的老師:恩......不錯。但如果這些信息提供方是別人寫的模塊,並且隨着版本的不同還經常變化,你怎麼辦? 懵懂的學生:... ... 智慧的老師:解決這些問題的方法是---持續性。 二、原理   持續性,也叫永久性。組件方提供 IPersistXXX 接口,調用者(容器)提供存儲介質,比如文件啦、內存啦、註冊表啦、流啦、文本啦......啦啦拉。需要保存的時候,調用者通過 IPersistXXX::Save() 接口函數讓組件去自己存儲屬性信息,而調用者根本不用關心存儲格式和存儲內容;需要還原狀態的時候,調用者打開存儲介質,然後同樣調用 IPersistXXX::Load() 接口函數讓組件自己去讀取屬性信息並完成初始化的設置。   目前,微軟定義瞭如下各種類型的持續性接口,足夠滿足你的需求了。我們只要在自己寫的組件中實現其中一個或幾個持續性接口,那麼調用者就可以按照統一的方式和我們的組件協商完成屬性信息的保存和狀態還原了。  

持續性接口 簡要說明
IPersist   所有持續性接口的根,下面的接口大多從它派生出來。這個接口很簡單,只有一個函數 GetClassID()它返回組件的 CLSID 號,以便調用者能保存這個號爲將來 CoCreateInstance() 啓動組件用。   實現這個函數也很簡單,只要返回你組件中的 CLSID_XXX 即可,或者比較省事的方法是返回 GetObjectCLSID() 。
IPersistStream

派生自 IPersist,並增加了4個函數,從流(IStream)中讀寫組件屬性信息。

IsDirty() 組件內部屬性是否發生了變化。爲調用者是否需要保存信息提供依據
Load() 從 IStream 中讀入信息,初始化組件屬性
Save() 把屬性信息保存到 IStream 中
GetSizeMax() 返回信息尺寸,以便調用者事先開闢空間
IPersistStreamInit 派生自 IPersistStream,並再增加了一個函數 InitNew() 用來完成一個默認的組件屬性初始化。 這個持續性接口是最常用的,本文示例中就實現了該接口。
IPersistMemory 和 IPersistStreamInit 類似,但使用的是內存塊,而不是大小可變化的 IStream 流。
IPersistStorage 和 IPersistStream 類似,但保存屬性信息使用的是存儲 IStorage,一個 IStorage 中可以有多個 IStream。
IPersistFile 和 IPersistStream 類似,但存儲介質爲文件。
IPersistPropertyBag   使用屬性包(屬性名、屬性值)的文本方式保存信息。在 IE 瀏覽器中,HTML 嵌入 ActiveX 控件通常使用這個方法。   在 HTML 中插入控件,<param name="屬性名稱" value="值"> 這樣的形式你應該見過吧?!   在下一回的文章中,我們介紹這個接口。因爲在 ActiveX 中,它太常用了。
IPersistPropertyBag2 擴展了 IPersistPropertyBag 接口。提供了更豐富一些的屬性管理用函數。
IPersistMoniker 用於命名(moniker)存儲和讀取狀態的持續性接口。
IPersistHistory 運行於 IE 上,想在用戶瀏覽 WEB 頁面時存儲和讀取狀態的持續性接口。

三、持續性接口組件的實現   示例程序分別在 vc6.0 和 vc.net 上實現了 IPersistStreamInit 接口的 COM 組件和調用舉例。組件完成的功能是計算素數,你第一次運行的時候,會得到第一個素數2,然後是3,5,7,11......下班時間到了,今天就運行到這裏。於是調用者開闢一個流來保存組件的屬性信息。明天繼續運行的時候,從流中原換組件狀態,開始了新的計算 13,17,19,23......   這個示例應用完全是假設性的,其實沒有什麼實用價值,只是演示了 IPersistStreamInit 接口的實現方法。另外,關於建立流(IStream)的方法,請參閱COM 組件設計與應用(一)》。 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;
}

四、小結   下載示例程序後,結合本文仔細閱讀代碼,並試着運行看看效果。如果你理解了,那麼你能自己實現 IPersistFile 接口嗎?你能自己實現 IPersistStorage 接口嗎?你實現的持續性接口越多,別人使用你的組件就越方便,也就是說你的組件就能大賣特賣啦,祝你爲中國軟件事業做貢獻的同時多多賺錢:-)下回我們用 IPersistPropertyBag 接口實現持續性屬性包功能,別忘了看呦......

楊老師,這節的示例中我覺得有個小BUG。 在 void CUseDlg::OnNew() {     .................              if( !spPSI ) AfxMessageBox( _T("組件沒有提供持續性接口") ); else { spPSI->InitNew(); GetDlgItem( IDC_NEXT )->EnableWindow( TRUE ); spPSI->Save(spStream, TRUE); } // 上一行的Save我覺得有必要,不然的話,點過重新開始後,再點從上次開始沒有任何效果了。 ( shuishi32 發表於 2007-6-19 14:51:00) ////////////////// 調用部分 ///////////////// 1、Load if(m_spPrimeFile) { CComQIPtr<IPersistFile> spPF; spPF = m_spPrimeFile; if(spPF) spPF->Load(lpwFileName, STGM_READ | STGM_SHARE_EXCLUSIVE); spPF.Release(); } 2、Save if(m_spPrimeFile) { CComQIPtr<IPersistFile> spPF; spPF = m_spPrimeFile; if(spPF) spPF->Save(lpwFileName, TRUE); spPF.Release(); } 3、Reset m_spPrimeFile->InitValue(); //Set m_nNow = 0; ( epubcn 發表於 2007-4-3 22:51:00) STDMETHODIMP CPrimeFile::Save(/*[in]*/LPCOLESTR pszFileName, /*[in]*/BOOL fRemember) { if( fRemember ) m_bDirty = false; _tcscpy_s(m_lptFilename, _MAX_PATH, pszFileName); //MessageBox(NULL,m_lptFilename,_T("Will save"),MB_OK|MB_ICONINFORMATION); HANDLE hFile = ::CreateFile(m_lptFilename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile) { DWORD dwBytes; ::WriteFile(hFile, &m_nNow, sizeof(long), &dwBytes, NULL); CloseHandle(hFile); } return S_OK; } STDMETHODIMP CPrimeFile::InitValue(void) { m_nNow = 0; return S_OK; } ( epubcn 發表於 2007-4-3 22:51:00) 使用IPersistFile處理的關鍵代碼如下: ////////////////// 接口部分 ///////////////// STDMETHODIMP CPrimeFile::Load(/*[in]*/LPCOLESTR pszFileName, /*[in]*/DWORD dwMode) { _tcscpy_s(m_lptFilename, _MAX_PATH, pszFileName); HANDLE hFile = ::CreateFile(m_lptFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile) { long lValue; DWORD dwReadBytes; ::ReadFile(hFile, &lValue, sizeof(long), &dwReadBytes, NULL); m_nNow = lValue; CloseHandle(hFile); } return S_OK; } ( epubcn 發表於 2007-4-3 22:51:00)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章