ATL持续化之IPersistPropertyBag

来源:互联网 发布:网络小说 毒 知乎 编辑:程序博客网 时间:2024/06/08 11:12

浅谈ATL中关于对象持续性
 com对于对象持续性并没有什么规定和支持。但是com提供了一些协议,利用这些协议就可以使用对象的持续性。
 在ATL中,提供了一系列介质接口IMedia,用于对象持续性支持。例如IStream  IStrorage IPropertyBag。为什么为了一个持续性提供三个接口呢?这是因为存在这样的一个事实:com组件容器并不知道用户想要保存对象信息到何处:网络流? 磁盘 or 属性包?
所以,它提供很多接口,用于满足不用持续性方式的需求。
  为了支持对象的永久持续性,ATL提供了IPersist接口,另外,定义了三个接口IPersistStream  IPersistStorage IPersistPropertyBag,它们派生自IPersist接口。IPersist从IUnknown派生,继续了其三个接口函数QueryInterface AddRef  Release,除此之外,它还声明了一个函数GetClassID()。当要进行永久持续化,保存对象信息的时候,首先调用GetClassID()函数获取要持续化对象的CLSID,保存下来,然后再调用对应持续化接口的Write保存有关此对象的一些信息;反之,复然对象的时候,首先调用GetClassID()函数获取对象的CLSID,创建对象,然后调用接口Read获取对象的信息初始化对象。
  下面就属性包IPersistPropertyBag来说说ATL对于永久持续性的支持。
  ATL提供了接口IPersistPropertyBag,我们可以让我们的组件派生于此接口,实现此接口的方法,重写其Load和Save函数,完成组件对象的持续化。但是如此处理过于繁琐。为了简化上层使用,ATL提供的IPersistPropertyBagImpl<T>模板类,顾名思义此模板类实现了IPersistPropertyBag接口,我们可以直接派生此类即可。
  IPersistPropertyBagImpl<T>模板中重写了Load() Save()方法。阅读一下Load()方法就知道,它主要做了下面几件事情:
  (1) 获取属性映射表 BEGIN_PROP_MAP(CLASSNAME) ....  END_PROP_MAP
  (2) 对于属性映射表中的每个表项,调用GetProperty获取其属性。
  关于(2),多说几句。首先:属性映射表的表项有些什么东东。我们先看看属性映射表以及获取静态函数表的函数定义:
BEGIN_PROP_MAP(CXXX)
 PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
 PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
 //or CLSID_NULL
 PROP_ENTRY("UiMode", 16,  CLSID_XXX)
 PROP_ENTRY("Url", 15, CLSID_XXX) 
END_PROP_MAP()
  上面就是一个简单的ATL组件的属性映射表。前面两个使用了prop_data_entry宏,后面的使用prop_entry,当然还可以用prop_entryex宏,具体的宏的意义
可以查看ATL代码,这里不罗嗦。简单一句话,就是往一个静态表中填充表项,表项--属性映射项,包括信息有:属性名 属性值 属性的dispid IDispatch接口iid等。
   其次,这里说了调用GetProperty方法获取属性。拿上面的属性映射表来说,前面两项是工程自己生成的,后面两个是手动添加的。PROP_ENTRY("UiMode", 16,  CLSID_XXX)带有三个参数:第一个是属性名"UiMode" 第二个是属性dispid,什么意思呢?也就是对应idl文件中的属性的dispid。为什么需要这里?因为永久持续性属性需要通过IDispatch接口,然后调用Invoke方法访问属性,访问哪个属性,就是通过此属性的dispid来确定的。说道这里就都明白了,调用GetProperty()方法,实际上就是通过查出Dispatch接口,然后调用Invoke方法来访问属性的。
  
   步骤二调用GetProperty()其实与就是为了检测一下此dispid对相应的属性的get方法是否存在,没太多其他作用
  
  (3) 调用IPropertyBag::Read()方法,读取指定属性名的值。
  HRESULT hr = pPropBag->Read(pMap[i].szDesc, &var, pErrorLog);
 
 (4) 调用CComPtr<IDispatch>::PutProperty将获取的属性值写入。方法:通过IDispatch::Invoke(dispid)调用对应的属性put函数。
  pDispatch.PutProperty(pMap[i].dispid, &var);
  
  也就是说,就通常的VARIANT兼容类型来说,如果我们想添加永久持续性属性,我们并不需要重写IPersistPropertyBagImpl::Load或者IPersistPropertyBag_Load函数,而只需要在属性映射表中添加相应的永久属性映射项即可,因为IPersistPropertyBagImpl类已经为我们把基本的东西都处理了。
 
  下面就说说如何添加一个固有属性:
  (1) 为组件对象添加基类:public IPersistPropertyBagImpl<CXP> 
  (2) 添加对应的接口映射:COM_INTERFACE_ENTRY(IPersistPropertyBag)
  (3) 属性映射表中添加你要的:
BEGIN_PROP_MAP(CXPPlayer)
 PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
 PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
 //or CLSID_NULL
 PROP_ENTRY("UiMode", 16,  CLSID_XPPlayer)
END_PROP_MAP()
  这里idl对应关于dispid = 16相关代码如下:
  [propget, id(16), helpstring("property UiMode")]    HRESULT UiMode([out, retval] INT* newVal);
 [propput, id(16), helpstring("property UiMode")]    HRESULT UiMode([in] INT newVal);
 
 如此一来,就定义了自己的固有属性UiMode的,你可以在你的控件使用中使用此属性。为什么需要这个呢?直接有了UiMode属性就可以了,为啥还搞个固有属性。你想想,类似UiMode这种属性,如果你仅仅以属性方式提供的话,更新UiMode会导致界面更新跳动。所以最好的方式就是使用固有属性,让控件在初始化的时候就进行相关的分支操作。要不然等到控件初始化之后,你在调用UiMode属性修改,就会导致界面跳动。
 在web中你可以使用固有属性,也就是你经常看到的<Param name =  value= >之类的东西,例如:
<object id="pplayer" classid="clsid:ABDC34RT-956E-49CC-B444-73AFE593AD34"  width="700" height="600" >
<PARAM NAME="UiMode" VALUE="0">
</object>

 

注:本文出自http://lanhy2000.blog.163.com/blog/static/4367860820124208285287/

原创粉丝点击