作者:
楊老師
下載源代碼
一、前言
我們寫程序,經常需要實現這樣的需求:
例一、程序運行產生一個窗口,用戶關閉的時候需要記錄窗口的位置,以便下次運行時保持位置不變;
例二、由于程序運行時間很長,今天執行一部分,明天繼續執行。那么在下次運行前要恢復前次的狀態;
... ... ... ...
智慧的老師:以上這些需求,如何實現呢?
懵懂的學生:這個簡單,只要在程序退出前提取必要的信息保存到文件中,下次運行時再從文件中讀出來,設置一下就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 文件中增加函數實現:
// IPersistSTDMETHODIMP Cxxx::GetClassID(/*[out]*/CLSID * pClassID){ *pClassID = GetObjectCLSID(); return S_OK;}// IPersistStreamSTDMETHODIMP 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;}// IPersistStreamInitSTDMETHODIMP Cxxx::InitNew(void){ 內部屬性數據默認初始化; 設置或清除內部表示數據變化的變量; return S_OK;}
四、小結 下載示例程序后,結合本文仔細閱讀代碼,并試著運行看看效果。如果你理解了,那么你能自己實現 IPersistFile 接口嗎?你能自己實現 IPersistStorage 接口嗎?
你實現的持續性接口越多,別人使用你的組件就越方便,也就是說你的組件就能大賣特賣啦,祝你為中國軟件事業做貢獻的同時多多賺錢:-)下回我們用 IPersistPropertyBag 接口實現持續性屬性包功能,別忘了看呦......