COM 組件設計與應用(十七)——持續性
作者:楊老師
下載源代碼
一、前言
我們寫(xiě)程序,經(jīng)常需要實(shí)現這樣的需求:
例一、程序運行產(chǎn)生一個(gè)窗口,用戶(hù)關(guān)閉的時(shí)候需要記錄窗口的位置,以便下次運行時(shí)保持位置不變;
例二、由于程序運行時(shí)間很長(cháng),今天執行一部分,明天繼續執行。那么在下次運行前要恢復前次的狀態(tài);
... ... ... ...
智慧的老師:以上這些需求,如何實(shí)現呢?
懵懂的學(xué)生:這個(gè)簡(jiǎn)單,只要在程序退出前提取必要的信息保存到文件中,下次運行時(shí)再從文件中讀出來(lái),設置一下就OK了。
智慧的老師:恩,不錯,這位同學(xué)的思想值得表?yè)P。
懵懂的學(xué)生:不好意思,這都要感謝老師的栽培,我對您的景仰如滔滔江水......
智慧的老師:別臭P了,我話(huà)還沒(méi)有說(shuō)完那......如果你需要提取和保存的信息很多,結構很復雜......怎么辦?
懵懂的學(xué)生:也好辦,我設計一個(gè)結構來(lái)記錄這些信息。
智慧的老師:恩......不錯。但如果這些信息提供方是別人寫(xiě)的模塊,并且隨著(zhù)版本的不同還經(jīng)常變化,你怎么辦?
懵懂的學(xué)生:... ...
智慧的老師:解決這些問(wèn)題的方法是---持續性。
二、原理
持續性,也叫永久性。組件方提供 IPersistXXX 接口,調用者(容器)提供存儲介質(zhì),比如文件啦、內存啦、注冊表啦、流啦、文本啦......啦啦拉。需要保存的時(shí)候,調用者通過(guò) IPersistXXX::Save() 接口函數讓組件去自己存儲屬性信息,而調用者根本不用關(guān)心存儲格式和存儲內容;需要還原狀態(tài)的時(shí)候,調用者打開(kāi)存儲介質(zhì),然后同樣調用 IPersistXXX::Load() 接口函數讓組件自己去讀取屬性信息并完成初始化的設置。
目前,微軟定義了如下各種類(lèi)型的持續性接口,足夠滿(mǎn)足你的需求了。我們只要在自己寫(xiě)的組件中實(shí)現其中一個(gè)或幾個(gè)持續性接口,那么調用者就可以按照統一的方式和我們的組件協(xié)商完成屬性信息的保存和狀態(tài)還原了。
| 持續性接口 | 簡(jiǎn)要說(shuō)明 | |
| IPersist | 所有持續性接口的根,下面的接口大多從它派生出來(lái)。這個(gè)接口很簡(jiǎn)單,只有一個(gè)函數 GetClassID()它返回組件的 CLSID 號,以便調用者能保存這個(gè)號為將來(lái) CoCreateInstance() 啟動(dòng)組件用。 實(shí)現這個(gè)函數也很簡(jiǎn)單,只要返回你組件中的 CLSID_XXX 即可,或者比較省事的方法是返回 GetObjectCLSID() 。 | |
| IPersistStream | 派生自 IPersist,并增加了4個(gè)函數,從流(IStream)中讀寫(xiě)組件屬性信息。 | |
| IsDirty() | 組件內部屬性是否發(fā)生了變化。為調用者是否需要保存信息提供依據 | |
| Load() | 從 IStream 中讀入信息,初始化組件屬性 | |
| Save() | 把屬性信息保存到 IStream 中 | |
| GetSizeMax() | 返回信息尺寸,以便調用者事先開(kāi)辟空間 | |
| IPersistStreamInit | 派生自 IPersistStream,并再增加了一個(gè)函數 InitNew() 用來(lái)完成一個(gè)默認的組件屬性初始化。 這個(gè)持續性接口是最常用的,本文示例中就實(shí)現了該接口。 | |
| IPersistMemory | 和 IPersistStreamInit 類(lèi)似,但使用的是內存塊,而不是大小可變化的 IStream 流。 | |
| IPersistStorage | 和 IPersistStream 類(lèi)似,但保存屬性信息使用的是存儲 IStorage,一個(gè) IStorage 中可以有多個(gè) IStream。 | |
| IPersistFile | 和 IPersistStream 類(lèi)似,但存儲介質(zhì)為文件。 | |
| IPersistPropertyBag | 使用屬性包(屬性名、屬性值)的文本方式保存信息。在 IE 瀏覽器中,HTML 嵌入 ActiveX 控件通常使用這個(gè)方法。 在 HTML 中插入控件,<param name="屬性名稱(chēng)" value="值"> 這樣的形式你應該見(jiàn)過(guò)吧?! 在下一回的文章中,我們介紹這個(gè)接口。因為在 ActiveX 中,它太常用了。 | |
| IPersistPropertyBag2 | 擴展了 IPersistPropertyBag 接口。提供了更豐富一些的屬性管理用函數。 | |
| IPersistMoniker | 用于命名(moniker)存儲和讀取狀態(tài)的持續性接口。 | |
| IPersistHistory | 運行于 IE 上,想在用戶(hù)瀏覽 WEB 頁(yè)面時(shí)存儲和讀取狀態(tài)的持續性接口。 | |
三、持續性接口組件的實(shí)現
示例程序分別在 vc6.0 和 vc.net 上實(shí)現了 IPersistStreamInit 接口的 COM 組件和調用舉例。組件完成的功能是計算素數,你第一次運行的時(shí)候,會(huì )得到第一個(gè)素數2,然后是3,5,7,11......下班時(shí)間到了,今天就運行到這里。于是調用者開(kāi)辟一個(gè)流來(lái)保存組件的屬性信息。明天繼續運行的時(shí)候,從流中原換組件狀態(tài),開(kāi)始了新的計算 13,17,19,23......
這個(gè)示例應用完全是假設性的,其實(shí)沒(méi)有什么實(shí)用價(jià)值,只是演示了 IPersistStreamInit 接口的實(shí)現方法。另外,關(guān)于建立流(IStream)的方法,請參閱《COM 組件設計與應用(一)》。
1、建立一個(gè) ATL 工程項目。
2、增加 ATL 組件類(lèi),vc.net 使用者注意不要選擇“屬性化編程”方式,其它的設置全部使用默認方法。當然你愿意適當地改變選擇也無(wú)所謂。
3、設計完成你的組件功能。
示例程序中,實(shí)現了一個(gè)接口函數 GetNext() 負責計算下一個(gè)素數。
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 接口函數。public:// IPersistSTDMETHOD(GetClassID)(/*[out]*/CLSID * pClassID);// IPersistStreamSTDMETHOD(IsDirty)(void);STDMETHOD(Load)(/*[in]*/IStream *pStm);STDMETHOD(Save)(/*[in]*/IStream *pStm,/*[in]*/BOOL fClearDirty);STDMETHOD(GetSizeMax)(/*[out]*/ULARGE_INTEGER *pcbSize);// IPersistStreamInitSTDMETHOD(InitNew)(void);手工在 cpp 文件中增加函數實(shí)現:
// IPersistSTDMETHODIMP Cxxx::GetClassID(/*[out]*/CLSID * pClassID){*pClassID = GetObjectCLSID();return S_OK;}// IPersistStreamSTDMETHODIMP Cxxx::IsDirty(void){if( 數據已經(jīng)改變,需要保存 ) return S_OK;else return S_FALSE;}STDMETHODIMP Cxxx::Load(/*[in]*/IStream *pStm){return pStm->Read( 讀到哪里, 讀多長(cháng)字節, NULL);}STDMETHODIMP Cxxx::Save(/*[in]*/IStream *pStm,/*[in]*/BOOL fClearDirty){if( fClearDirty ) 清除內部表示數據變化的變量;return pStm->Write( 需要保存的數據指針, 寫(xiě)多長(cháng)字節, NULL );}STDMETHODIMP Cxxx::GetSizeMax(/*[out]*/ULARGE_INTEGER *pcbSize){pcbSize->LowPart = 需要保存數據長(cháng)度的低位;pcbSize->HighPart = 需要保存數據長(cháng)度的高位;// 一般都是0,難道你的數據長(cháng)度都超過(guò)了 4G?return S_OK;}// IPersistStreamInitSTDMETHODIMP Cxxx::InitNew(void){內部屬性數據默認初始化;設置或清除內部表示數據變化的變量;return S_OK;}四、小結聯(lián)系客服