COM组件设计与应用(十八)

COM组件设计与应用(十八)

属性包

作者:杨老师

下载源代码

一、前言

  书接上回,本回着落在介绍属性包 IPersistPropertyBag 接口的实现方法和调用方式。属性包,是以“名称 - 值”的方式提供组件持续性的支持,而“名称 - 值”恰恰又适合于用文本方式来表现。下面的片段是在 HTML 中插入 Microsoft MonthView Control ActiveX 控件后的样式:

<object classid="clsid:232E456A-87C3-11D1-8BE3-0000F8754DA1" id="MonthView1">
 <param name="_ExtentX" value="9393">
 <param name="_ExtentY" value="4974">
 <param name="_Version" value="393216">
 <param name="ForeColor" value="0">
 <param name="MaxSelCount" value="7">
 <param name="MonthColumns" value="1">
 <param name="CurrentDate" value="38632">
 <param name="MaxDate" value="2958465">
 <param name="MinDate" value="-53688">
</object>

以文本方式保存组件属性,比较直观、容易修改,上面 HTML 示例中的 <param name="属性名" value="值"> 就很清晰。下面开始介绍如何在组件中实现 IPersistPropertyBag 接口。

二、组件的实现
(1)vc6.0 开发步骤
1、建立一个工作空间(WorkSpace)。
2、在这个工作空间中,建立 ATL 工程,示例程序工程为 Simple18。
3、增加 ATL 对象类,默认全部选项。示例程序中的 ATL 对象短名称是 Property。
4、增加一些属性。在以前的章回中,我们只介绍了增加接口函数的方法,由于今天是首次增加接口属性,所以稍微细致一些。步骤是,在ClassView卡片中选择接口(IProperty)后,执行鼠标右键菜单"Add Property..."



5、增加 BSTR 类型的接口属性 str,同样的方式,再增加一个 long 型的接口属性 interger。在示例程序中,这两个属性其实只为演示,并没有实际的意义。



6、接口中的属性,多数情况下会对应对象内部的一个成员变量,因此我们现在要添加成员变量。选择对象类名,执行鼠标右键菜单"Add Member Variable...."



7、添加两个成员变量,一个是 CComBSTR m_str 对应于接口属性 str;另一个是 long m_integer 对应于接口属性 integer。



(2)vc.net 开发步骤
1、建立一个空白解决方案。
2、在解决方案中,新增 ATL 项目。示例程序中项目名称叫 Simple18, 注意不要选择“属性化编程”方式。
3、添加 ATL 类。选择 “ATL 的简单对象”。默认全部选项。示例程序中 ATL 类短名称为 Property,类名称为 CMyProperty。(注1)
4、 增加一些属性。在以前的章回中,我们只介绍了增加接口函数的方法,由于今天是首次增加接口属性,所以稍微细致一些。步骤是,在类视图卡片中选择接口(IProperty)后,执行鼠标右键菜单"添加属性..."



5、增加 BSTR 类型的接口属性 str,同样的方式,再增加一个 long 型的接口属性 interger。在示例程序中,这两个属性其实只为演示,并没有实际的意义。



6、接口中的属性,多数情况下会对应对象内部的一个成员变量,因此我们现在要添加成员变量。选择对象类名,执行鼠标右键菜单"添加变量...."



7、添加两个成员变量,一个是 CComBSTR m_str 对应于接口属性 str;另一个是 long m_integer 对应于接口属性 integer。


(3)实现代码
  至此,我们组件的框架已经完成,下面该完成函数函数的实现了:

STDMETHODIMP Cxxx::get_str(BSTR* pVal)
{
 *pVal = m_str.Copy();
 return S_OK;
}

STDMETHODIMP Cxxx::put_str(BSTR newVal)
{
 m_str = newVal;
 return S_OK;
}

STDMETHODIMP Cxxx::get_integer(LONG* pVal)
{
 *pVal = m_integer;
 return S_OK;
}

STDMETHODIMP Cxxx::put_integer(LONG newVal)
{
 m_integer = newVal;
 return S_OK;
}

没有什么复杂的,就是实现 str、integer 两个属性值的设置和读取功能。
(4)添加 IPersistPropertyBag 接口
  还记得我们在上回书中如何添加 IPersistStreamInit 的吗?添加 IPersistPropertyBag 的方法也一样,但这次我们换一个方式,即我们不从 IPersistPropertyBag 派生,而是从 IPersistPropertyBagImpl<> 派生。在 ATL 中,系统帮我们已经完成了很多接口的默认实现,我们只要从 IxxxImpl<> 派生,然后再添加一些必要的映射和变量,就可以了。这样显然要比自己去实现接口的所有函数要简单许多了。其实,如果你明白了本回 IPersistPropertyBagImpl<> 派生的方法后,你完全可以修改前回书中的实现方法,从 IPersistStreamInit 派生改进为从 IPersistStreamInitImpl<> 派生。

class ATL_NO_VTABLE Cxxx : 
 public CComObjectRootEx<...>,
 public CComCoClass<...>,
 public IDispatchImpl<...>,
 public IPersistPropertyBagImpl<Cxxx> // 手工添加派生类
{
... ... ...

BEGIN_COM_MAP(Cxxx)
 ... ... ...
 COM_INTERFACE_ENTRY(IPersistPropertyBag) // 手工添加接口表
END_COM_MAP()
... ... ...
 // 手工添加属性映射表,这是 IPersistXXXImpl 所必须的。
 // 将来你在写 ActiveX 的时候,ATL 向导会帮我们添加属性映射表
BEGIN_PROP_MAP(Cxxx)
 // 参数:"属性名称", 接口属性序号(见IDL文件), 属性页对话窗
 PROP_ENTRY("str", 1, CLSID_NULL)
 PROP_ENTRY("integer", 2, CLSID_NULL)
END_PROP_MAP()
... ... ...
public:
 ... ... ...
 // 这个成员变量,是 IPersistXXXImpl 所必须的
 bool m_bRequiresSave; // 表示属性数据是否已经改变而需要保存
};

  我们只要手工添加以上内容,而不用自己写任何 IPersistPropertyBag 接口的函数,多简单呀!天空出彩霞呀,地上开红花呀......会唱这只歌的同学请举手,每个人奖励 vckbase 的专家分 500 !

三、调用者的实现
  我们在阅读 MSDN 关于 IPersistPropertyBag 接口函数的时候,你会发现还需要一个接口 IPropertyBag 与之配合才能实现属性包功能。而 IPropertyBag 则需要我们在调用者(容器)中来实现该接口。它们之间的关系如下:



  前面几回书中,我们已经学会了从 IUnknown 派生类,也学会了从 IDispatch 派生类,也学会了从 ICallBack 派生类......同样,这回我们要从 IPropertyBag 派生了。在示例程序中,我们添加了一个类 CPropertyBag::public IPropertyBag,同时重载了所有的虚函数。

 

STDMETHODIMP CPropertyBag::QueryInterface(const struct _GUID &iid,void ** ppv)
{
 *ppv = this;
 return S_OK;
}

ULONG __stdcall CPropertyBag::AddRef(void)
{ return 1;  } // 做个假的就可以,因为反正这个对象在程序结束前是不会退出的

ULONG __stdcall CPropertyBag::Release(void)
{ return 0;  } // 做个假的就可以,因为反正这个对象在程序结束前是不会退出的

STDMETHODIMP CPropertyBag::Read(LPCOLESTR pszPropName,VARIANT *pVar,IErrorLog *pErrorLog)
{
 // 根据 pszPropName 指定的属性名称,你要提供该属性的值。
 // 而值的数据类型已经在 pVal->vt 中指定了。
 if( 如果能提供指定的数据 ) return S_OK;
 else return E_FAIL;
}

STDMETHODIMP CPropertyBag::Write(LPCOLESTR pszPropName,VARIANT *pVar)
{
 // 根据 psaPropName 指定的属性名称和 pVar 提供的值
 // 你保存到文本中去吧。
 return S_OK;
}

  以上是调用者(容器)程序的关键部分,其它的管理和协调部分,读者去阅读示例程序代码。编译注册组件,并运行调用者示例程序,显示如下:



  在编辑窗口中你可以随便指定 str 和 interger 的值,然后“启动组件”,那么你设定的属性值就会在启动组件的同时,通过 IPersistPropertyBag 接口设置到组件中(还原了持续性的环境)。而后,你就可以在下面的 Property 分组操作中,“设置/读取”组件的属性了。当“关闭组件”的时候,程序通过调用 IPersistPropertyBag 接口函数,又重新取得组件的属性名称和值保存到编辑窗的文本中了。

四、小结
  理解了本回属性包接口的功能,你就能体会出 IE 是如何装载 ActiveX (注2)控件并设置控件的状态了。


注1:在 vc.net 中,由于系统已经有 CProperty 类,所以这里我们改换名称为 CMyProperty。
注2:通过十八回的学习,我们已经了解组件的一些常用接口,为我们学习 ActiveX 的组件编程打下了基础。下回书,我们就开始学习 ActiveX。

 

 

本文属转载,原文见:http://www.vckbase.com/document/viewdoc/?id=1547

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
组件基础 1 软件开发的阶段 1.1 结构化编程 采用自顶向下的编程方式,划分模块 和功能的一种编程方式。 1.2 面向对象编程 采用对象的方式,将程序抽象成类, 模拟现实世界,采用继承、多态的方式 设计软件的一种编程方式。 1.3 面向组件编程 将功能和数据封装成二进制代码,采用 搭积木的方式实现软件的一种编程方式。 2 组件和优点 2.1 组件 - 实际是一些可以执行的二进 制程序,它可以给其他的应用程序、操 作系统或其他组件提供功能 2.2 优点 2.2.1 可以方便的提供软件定制机制 2.2.2 可以很灵活的提供功能 2.2.3 可以很方便的实现程序的分布式 开发。 3 组件的标准 - COMComponent Object Model ) 3.1 COM是一种编程规范,不论任何开发语言 要实现组件都必须按照这种规范来实现。 组件和开发语言无关。 这些编程规范定义了组件的操作、接口的 访问等等。 3.2 COM接口 COM接口是组件的核心,从一定程度上 讲"COM接口是组件的一切". COM接口给用户提供了访问组件的方式. 通过COM接口提供的函数,可以使用组件 的功能. 4 COM组件 4.1 COM组件-就是在Windows平台下, 封装在动态库(DLL)或者可执行文件(EXE) 中的一段代码,这些代码是按照COM的 规范实现. 4.2 COM组件的特点 4.2.1 动态链接 4.2.2 与编程语言无关 4.2.3 以二进制方式发布 二 COM接口 1 接口的理解 DLL的接口 - DLL导出的函数 类的接口 - 类的成员函数 COM接口 - 是一个包含了一组函数指针 的数据结构,这些函数是由组件实现的 2 C++的接口实现 2.1 C++实现接口的方式,使用抽象类 定义接口. 2.2 基于抽象类,派生出子类并实现 功能. 2.3 使用 interface 定义接口 interface ClassA { }; 目前VC中,interface其实就是struct 3 接口的动态导出 3.1 DLL的实现 3.1.1 接口的的定义 3.1.2 接口的实现 3.1.3 创建接口的函数 3.2 DLL的使用 3.2.1 加载DLL和获取创建接口的函数 3.2.2 创建接口 3.2.3 使用接口的函数 4 接口的生命期 4.1 问题 在DLL中使用new创建接口后,在用户 程序使用完该接口后,如果使用delete 直接删除,会出现内存异常. 每个模块有自己的内存堆(crtheap) EXE - crtheap DLL - crtheap new/delete/malloc/free默认情况 下都是从自己所在模块内存堆(crtheap) 中分配和施放内存.而各个模块的 这个内存堆是各自独立.所以在DLL中 使用new分配内存,不能在EXE中delete. 4.2 引用计数和AddRef/Release函数 引用计数 - 就是一个整数,作用是 表示接口的使用次数 AddRef - 增加引用计数 +1 Release - 减少引用计数 -1, 如果 当引用计数为0,接口被删除 4.3 使用 4.3.1 创建接口 4.3.2 调用AddRef,增加引用计数 4.3.3 使用接口 4.3.4 调用Release,减少引用计数 4.4 注意 4.4.1 在调用Release之后,接口指针 不能再使用 4.4.2 多线程情况下,接口引用计数 要使用原子锁的方式进行加减 5 接口的查询 5.1 每个接口都具有唯一标识 GUID 5.2 实现接口查询函数 QueryInterface 6 IUnknown 接口 6.1 IUnknown是微软定义的标准接口 我们实现所有接口就是继承这个接口 6.2 IUnknown定义了三个函数 QueryInterface 接口查询函数 AddRef 增加引用计数 Release 减少引用计数 7 接口定义语言 - IDL(Interface Definition Language ) 7.1 IDL和MIDL IDL - 定义接口的一种语言,与开发 语言无关. MIDL.EXE - 可以将IDL语言定义接口, 编译成C++语言的接口定义 7.2 IDL的基础 import "XXXX.idl" [ attribute ] interface A : interface_base { } 7.2.1 Import 导入,相当于C++的 #include 7.2.2 使用"[]"定义区域,属性描述 关键字 1) object - 后续是对象 2) uuid - 定义对象GUID 3) helpstring - 帮助信息 4) version - 版本 5) point_default - 后续对象 中指针的默认使用方式 比如: uniqune - 表示指针可以 为空,但是不能修改 7.2.3 对象定义 1) 父接口是IUnknown接口 2) 在对象内添加函数,函数定义必须 是返回 HRESULT. HRESULT是32位整数,返回函数是否 执行成功,需要使用 SUCCESSED和 FAILED宏来判断返回值.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值