【转载】COM 组件设计与应用(十七)——持续性

原文:http://vckbase.com/index.php/wv/1264.html

一、前言

我们写程序,经常需要实现这样的需求:

例一、程序运行产生一个窗口,用户关闭的时候需要记录窗口的位置,以便下次运行时保持位置不变;

例二、由于程序运行时间很长,今天执行一部分,明天继续执行。那么在下次运行前要恢复前次的状态;

... ... ... ...

智慧的老师:以上这些需求,如何实现呢?

懵懂的学生:这个简单,只要在程序退出前提取必要的信息保存到文件中,下次运行时再从文件中读出来,设置一下就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 中插入控件, 这样的形式你应该见过吧?!
   在下一回的文章中,我们介绍这个接口。因为在 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 接口实现持续性属性包功能,别忘了看呦......

时间: 2024-12-24 11:50:05

【转载】COM 组件设计与应用(十七)——持续性的相关文章

【转载】COM 组件设计与应用(四)——简单调用组件

原文:http://vckbase.com/index.php/wv/1211.html 一.前言 同志们.朋友们.各位领导,大家好. VCKBASE 不得了, 网友众多文章好. 组件设计怎么学? 知识库里闷头找! 摘自---杨老师打油集录 在 VCKBASE 的顶力支持下,在各位网友回帖的鼓励下,我才能顺利完成系列论文的前三回.书到本回,我们终于开始写代码啦.写点啥那?恩,有了!咱们先从如何调用现成的简单的组件开始吧,同时也顺便介绍一些相关的知识. 二.组件的启动和释放 在第三回中,大家用“小

【转载】COM 组件设计与应用(一)——起源及复合文件

原文:http://vckbase.com/index.php/wv/1201.html 一.前言 公元一九九五年某个夜黑风高的晚上,我的一位老师跟我说:“小杨呀,以后写程序就和搭积木一样啦.你赶快学习一些OLE的技术吧......”,当时我心里就寻思 :“开什么玩笑?搭积木方式写程序?再过100年吧......”,但作为一名听话的好学生,我开始在书店里“踅摸”(注1)有关OLE的书籍(注2).功夫不负有心人,终于买到了我的第一本COM书<OLE2 高级编程技术>,这本800多页的大布头花费了

【转载】COM 组件设计与应用(九)——IDispatch 接口 for VC6.0

原文: http://vckbase.com/index.php/wv/1224.html 一.前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用.非常有用.非常精彩的一个 COM 功能.由于 WORD.EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML.ASP.JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性. 如果你使用 vc6.0 的开发环境,

【转载】COM 组件设计与应用(八)——实现多接口

原文:http://vckbase.com/index.php/wv/1219.html 一.前言 从第五回开始到第七回,咱们用 ATL 写了一个简单的 COM 组件,之所以说简单,是因为在组件中,只实现了一个自定义(custom)的接口 IFun.当然如果想偷懒的话,我们可以把 200 个函数都加到这一个接口中, 果真如此的话,恐怕就没有人喜欢使用我们这个组件了.一个组件既然可以提供多个接口,那么我们在设计的时候,就应该按照函数的功能进行分类,把不同功能分类的函数用多个接口表现出来.这样可以有

【转载】COM 组件设计与应用(十一)—— IDispatch 及双接口的调用

原文:http://vckbase.com/index.php/wv/1236.html 一.前言 前段时间,由于工作比较忙,没有能及时地写作.其间收到了很多网友的来信询问和鼓励,在此一并表示感谢.咳......我也需要工作来养家糊口呀...... 上回书介绍了两种方法来写自动化(IDispatch)接口的组件程序,一是用 MFC 方式编写“纯粹”的IDispatch 接口:二是用 ATL 方式编写“双接口”的组件. 二.IDispatch 接口和双接口 使用者要想调用普通的 COM 组件功能,

【转载】COM 组件设计与应用(十)——IDispatch 接口 for VC.NET

原文:http://vckbase.com/index.php/wv/1225.html 一.前言 终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用.非常有用.非常精彩的一个 COM 功能.由于 WORD.EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML.ASP.JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性. 如果你使用 vc6.0 的开发环境,请

【转载】COM 组件设计与应用(五)——用 ATL 写第一个组件

原文:http://vckbase.com/index.php/wv/1215.html 一.前言 1.如果你在使用 vc5.0 及以前的版本,请你升级为 vc6.0 或 vc.net 2003: 2.如果你在使用 vc6.0 (ATL 3.0)请阅读本回内容: 3.如果你在使用 vc.net(ATL 7.0)请阅读下回内容:(当然读读本文内容也不错) 4.这第一个组件,除了所有 COM 组件必须的 IUnknown 接口外,我们再实现一个自己定义的接口 IFun,它有两个函数: Add()完成

【转载】COM 组件设计与应用(六)——用 ATL 写第一个组件

原文:http://vckbase.com/index.php/wv/1216.html 一.前言 1.与 <COM 组件设计与应用(五)>的内容基本一致.但本回讲解的是在 vc.net 2003 下的使用方法,即使你不再使用vc6.0,也请和上一回的内容,参照比对. 2.这第一个组件,除了所有 COM 组件必须的 IUnknown 接口外,我们再实现一个自己定义的接口 IFun,它有两个函数: Add()完成两个数值的加法,Cat()完成两个字符串的连接. 3.下面......好好听讲! 开

【转载】COM 组件设计与应用(十八)——属性包

原文:http://vckbase.com/index.php/wv/1265.html 一.前言 书接上回,本回着落在介绍属性包 IPersistPropertyBag 接口的实现方法和调用方式.属性包,是以“名称 - 值”的方式提供组件持续性的支持,而“名称 - 值”恰恰又适合于用文本方式来表现.下面的片段是在 HTML 中插入 Microsoft MonthView Control ActiveX 控件后的样式: <object classid="clsid:232E456A-87C