zoukankan      html  css  js  c++  java
  • 【转载】COM 组件设计与应用(八)——实现多接口

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

    一、前言

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

    1、一个接口中的函数个数有限、功能集中,使用者容易学习、记忆和调用。一个接口到底提供多少个函数合适那?答案是:如果你是黑猩猩,那么一个接口最多3个函数,如果你是人,那么一个接口最好不要超过7个函数。(注1)

    2、容易维护。至少你肉眼搜索的时候也方便一些呀。

    3、容易升级。当我们给组件增加函数的时候,不要修改已经发表的接口,而是提供一个新的接口来完成功能扩展。(注2)

    本回书着落在------如何实现一个组件,多个接口。

    二、接口结构

    图一、组件A有2个自定义接口,组件B是A的升级

    某日,我们设计了组件A,它有2个自定义(custom)接口。IMathe 有函数Add()完成整数加法,IStr 有函数Cat()完成字符串连接。忽一日,我们升级组件A到B,欲增加一个函数 Mul() 完成整数的乘法。注意,由于我们已经发表了组件A,因此我们不能把这个函数安排到老接口 IMathe 中了。解决方法是再定义一个新接口 IMathe2,在新接口中增加 Mul() 函数并依旧保留 Add() 函数。这样,老用户不知道新接口 IMathe2 的存在,他仍然使用旧接口 IMathe;而新用户则可以抛弃 IMathe,直接使用 IMathe2 的新接口功能。看,多平顺的升级方式呀!

    三、实现

    3-1、首先用 ATL 实现一个自定义(custom)接口 IMathe 的COM组件,在接口中完成 Add()整数加法函数。注意!!!一定是自定义(custom)的接口(dual 双接口以后再介绍)。如果你不了解这个操作,请重新阅读“第五回”或“第六回”。

    3-2、查看 IDL 文件。完成上一个步骤后,打开IDL文件,内容如下:(名称及 UUID 会和你程序中的IDL有所不同)

     import "oaidl.idl";
     import "ocidl.idl";
    	[
    		object,
    		uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
    	
    		helpstring("IMathe Interface"),
    		pointer_default(unique)
    	]
    	interface IMathe : IUnknown
    	{
    		[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2, [out,retval] long *pnVal);
    	};
    
     [
    	uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
    	version(1.0),
    	helpstring("Simple3 1.0 Type Library")
     ]
     library SIMPLE3Lib
     {
    	importlib("stdole32.tlb");
    	importlib("stdole2.tlb");
    
    	[
    		uuid(C6F241E2-43F6-4449-A024-B7340553221E),
    		helpstring("Mathe Class")
    	]
    	coclass Mathe
    	{
    		[default] interface IMathe;
    	};
    3 };
    

      

    1-2 引入 IUnknown 和ATL已经定义的其它接口描述文件。import 类似与 C 语言中的 #include
    3-12 一个接口的完整描述
    4 object 表示本块描述的是一个接口。IDL文件是借用了PRC远程数据交换格式的说明方法
    5 uuid(......) 接口的 IID,这个值是 ATL 自动生成的,可以手工修改或用 guidgen.exe 产生(注3)
    6 在某些软件或工具中,能看到这个提示
    7 定义接口函数中参数所使用指针的默认属性(注4)
    9 接口叫 IMathe 派生自 IUnknown,于是 IMathe 接口的头三个函数一定就是QueryInterface,AddRef和Release
    10-12 接口函数列表
    13-30 类型库的完整描述(类型库的概念以后再说),下面所说明的行,是需要先了解的
    18 #import 时候的默认命名空间
    23 组件的 CLSID,CoCreateInstance()的第一个参数就是它
    27-29 接口列表
    28 [default]表示谁提供了IUnknown接口

    3-3、手工修改IDL文件,黑体字部分是手工输入的。完成后保存。

    import "oaidl.idl";
    import "ocidl.idl";
    	[
    		object,
    		uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
    	
    		helpstring("IMathe Interface"),
    		pointer_default(unique)
    	]
    	interface IMathe : IUnknown
    	{
    		[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2, [out,retval] long *pnVal);
    	};  [ // 所谓手工输入,其实也是有技巧的:把上面的接口描述(IMathe)复制、粘贴下来,然后再改更方便哈 object,
    		uuid(072EA6CB-7D08-4E7E-B2B7-B2FB0B875595), // 手工或用工具产生的 IID helpstring("IStr Interface"),
    		pointer_default(unique)
    	]
    	interface IStr : IUnknown
    	{ // 目前还没有任何接口函数 };  [
    	uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
    	version(1.0),
    	helpstring("Simple3 1.0 Type Library")
    ]
    library SIMPLE3Lib
    {
    	importlib("stdole32.tlb");
    	importlib("stdole2.tlb");
    
    	[
    		uuid(C6F241E2-43F6-4449-A024-B7340553221E),
    		helpstring("Mathe Class")
    	]
    	coclass Mathe
    	{
    		[default] interface IMathe;  interface IStr; // 别忘了呦,这里还有一个那
    	};
    };
    

      

    3-4、打开头文件(Mathe.h),手工增加类的派生关系和接口入口表 ,然后保存。

    class ATL_NO_VTABLE CMathe : 
    	public CComObjectRootEx ,
    	public CComCoClass ,
    	public IMathe,	// 别忘了,这里加一个逗号 public IStr // 增加一个基类
    {
    public:
    	CMathe()
    	{
    	}
    
    DECLARE_REGISTRY_RESOURCEID(IDR_MATHE)
    
    DECLARE_PROTECT_FINAL_CONSTRUCT()
    
    BEGIN_COM_MAP(CMathe)	// 接口入口表。这里填写的接口,才能被QueryInterface()找到
    	COM_INTERFACE_ENTRY(IMathe) COM_INTERFACE_ENTRY(IStr) END_COM_MAP()
    

      

    3-5、好了,一切就绪。接下来,就可以在 IStr 接口中增加函数了。示例程序中增加一个字符串连接功能的函数:
    HRESULT Cat([in] BSTR s1, [in] BSTR s2, [out,retval] BSTR *psVal); 如果你不知道如何做,请重新阅读前三回的内容。

    四、接口升级

    我们这个组件已经发行了,但忽然一天我们需要在 IMathe 接口上再增加一个函数......不行!绝对不能在 IMathe 上直接修改!怎么办?解决方法是------再增加一个接口,我们就叫 IMathe2 吧,如果以后还要增加函数,那么我们再增加一个接口叫 IMathe3......子子孙孙,无穷尽也。

    4-1、修改 IDL 文件,其实如果你理解了上面一小节的内容,再增加一个接口是很简单的事情了。

    import "oaidl.idl";
    import "ocidl.idl";
    	[
    		object,
    		uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
    	
    		helpstring("IMathe Interface"),
    		pointer_default(unique)
    	]
    	interface IMathe : IUnknown
    	{
    		[helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2, [out,retval] long *pnVal);
    	};
    
    	[
    		object,
    		uuid(072EA6CB-7D08-4E7E-B2B7-B2FB0B875595),
    	
    		helpstring("IStr Interface"),
    		pointer_default(unique)
    	]
    	interface IStr : IUnknown
    	{
    		[helpstring("method Cat")] HRESULT Cat([in] BSTR s1, [in] BSTR s2, [out,retval] BSTR *psVal);
    	};  [
    		object,
    		uuid(072EA6CC-7D08-4E7E-B2B7-B2FB0B875595),
    	
    		helpstring("IMathe2 Interface"),
    		pointer_default(unique)
    	]
    	interface IMathe2 : IUnknown
    	{ // 下面这个Add()函数,只有IDL中的声明,而不用增加任何程序代码,因为这个函数早在 IMathe 中就已经实现了 [helpstring("method Add")] HRESULT Add([in] long n1, [in] long n2, [out,retval] long *pnVal);
    	}; [
    	uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
    	version(1.0),
    	helpstring("Simple3 1.0 Type Library")
    ]
    library SIMPLE3Lib
    {
    	importlib("stdole32.tlb");
    	importlib("stdole2.tlb");
    
    	[
    		uuid(C6F241E2-43F6-4449-A024-B7340553221E),
    		helpstring("Mathe Class")
    	]
    	coclass Mathe
    	{
    		[default] interface IMathe;
    		interface IStr; interface IMathe2; // 别忘了,这里还有一行呢!
    	};
    };
    

      

    4-2、打开头文件,增加派生关系和接口入口表

    class ATL_NO_VTABLE CMathe : 
    	public CComObjectRootEx ,
    	public CComCoClass ,
    	public IMathe,
    	public IStr,	// 这里增加一个逗号 public IMathe2 {
    public:
    	CMathe()
    	{
    	}
    
    DECLARE_REGISTRY_RESOURCEID(IDR_MATHE)
    
    DECLARE_PROTECT_FINAL_CONSTRUCT()
    
    BEGIN_COM_MAP(CMathe)
    	COM_INTERFACE_ENTRY(IMathe)
    	COM_INTERFACE_ENTRY(IStr) COM_INTERFACE_ENTRY(IMathe2) END_COM_MAP()
    

      

    4-3、示例程序中,增加了一个整数乘法函数:

    HRESULT Mul([in] long n1, [in] long n2, [out,retval] long *pnVal); 如果你不知道如何做,那就别学了:-( 都讲好几遍了,怎么还不掌握呢?知道狗熊是怎么死的吗?(注5)

    4-4、因为我们的组件升级了,于是也应该修改版本号了(这不是必须的,但最好修改一下)。打开注册表文件(.rgs) 把有关ProgID的版本 "Mathe.1" 修改为"Mathe.2"。另外如果你愿意,把IDL文件中的 version 和提示文字一并修改一下。这里就不再粘贴文件内容了,因为很简单,大家下载示例程序(注6)后,自己看吧。

    五、小结

    为祖国的软件事业而奋斗!

    下回书介绍“自动化”--- IDispatch 接口,好玩的很!谢谢关注:-)

    注1: 黑猩猩的瞬时记忆量是3,人类的瞬时记忆量是7。科学家做过实验,当着面,把一块糖扣在3个碗的其中之一,黑猩猩能立刻准确找到,但如果超过3个碗,猩猩就晕了......如果给你看一串数字,然后立刻让你说出来,一般的人只会记得其中的7个。

    注2:组件一经发表,就不要修改已有接口。这样软件的升级才能做到“鲁棒”性。

    注3:guidgen.exe 工具,在《COM 组件设计与应用(二)》中已经介绍。

    注4:组件函数对内存指针的处理,以后有专门的章回讨论。

    注5:笨死的!

    注6:示例程序有两部分,分别是vc6.0版本和vc.net 2003 版本。

  • 相关阅读:
    联想 Vibe Shot(Z90-3) 免recovery 获取ROOT权限 救砖 VIBEUI V3.1_1625
    联想 Z5S(L78071)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.370
    联想 Z5(L78011) 免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 10.5.254
    联想 S5 Pro(L78041)免解锁BL 免rec 保留数据 ROOT Magisk Xposed 救砖 ZUI 5.0.123
    第二阶段 冲刺八
    第二阶段 冲刺七
    第二阶段 冲刺六
    第二阶段 冲刺五
    代码大全阅读笔记03
    学习进度十二
  • 原文地址:https://www.cnblogs.com/zhehan54/p/5024383.html
Copyright © 2011-2022 走看看