原文: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 版本。

【转载】COM 组件设计与应用(八)——实现多接口的更多相关文章

  1. 【转载】COM 组件设计与应用(七)——编译、注册、调用

    原文:http://vckbase.com/index.php/wv/1218.html 一.前言 上两回中,咱们用 ATL 写了第一个 COM 组件程序,这回中,主要介绍编译.册和调用方法.示例程序 ...

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

    原文:http://vckbase.com/index.php/wv/1216.html 一.前言 1.与 <COM 组件设计与应用(五)>的内容基本一致.但本回讲解的是在 vc.net ...

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

    原文:http://vckbase.com/index.php/wv/1215.html 一.前言 1.如果你在使用 vc5.0 及以前的版本,请你升级为 vc6.0 或 vc.net 2003: 2 ...

  4. 【转载】COM 组件设计与应用(二)——GUID 和 接口

    原文:http://vckbase.com/index.php/wv/1203.html COM 组件设计与应用 系列文章:http://vckbase.com/index.php/piwz?& ...

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

    原文:http://vckbase.com/index.php/wv/1264.html 一.前言 我们写程序,经常需要实现这样的需求: 例一.程序运行产生一个窗口,用户关闭的时候需要记录窗口的位置, ...

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

    原文:http://vckbase.com/index.php/wv/1236.html 一.前言 前段时间,由于工作比较忙,没有能及时地写作.其间收到了很多网友的来信询问和鼓励,在此一并表示感谢.咳 ...

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

    原文:http://vckbase.com/index.php/wv/1225.html 一.前言 终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用 ...

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

    原文: http://vckbase.com/index.php/wv/1224.html 一.前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常 ...

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

    原文:http://vckbase.com/index.php/wv/1211.html 一.前言 同志们.朋友们.各位领导,大家好. VCKBASE 不得了, 网友众多文章好. 组件设计怎么学? 知 ...

随机推荐

  1. 从.net角度分析 异步和多线程

    线程 进程 CPU目前都是多核心的,相当于一个大脑几块可以同时工作. 超线程CPU是指在一块CPU中,用虚拟方法将一个物理核心模拟成多个核心(如:一个单物理核心,模拟成二个核心,即所谓的二线程.)只有 ...

  2. 审计系统---paramiko模块学习

    paramiko模块学习 [更多参考]http://www.cnblogs.com/wupeiqi/articles/4963027.html [paramiko的Demo实例]https://git ...

  3. apache的AllowOverride以及Options使用详解

    通常利用Apache的rewrite模块对 URL 进行重写的时候, rewrite规则会写在 .htaccess 文件里.但要使 apache 能够正常的读取.htaccess 文件的内容,就必须对 ...

  4. docker及服务器遇到的坑

    目录 DNS不可用 修改docker查找源 容器保持固定ip 查看docker连接 容器间通信 容器拷贝数据 php连接docker mysql 8.0出错authentication method ...

  5. (1)线程的同步机制 (2)网络编程的常识 (3)基于tcp协议的编程模型

    1.线程的同步机制(重点)1.1 基本概念 当多个线程同时访问同一种共享资源时可能会造成数据的覆盖和不一致等问题,此时就需要对线程之间进行协调和通信,该方式就叫线程的同步机制. 如: 2003年左右 ...

  6. 第八章 计时器(DIGCLOCK)

    /*-------------------------------------- DIGCLOCK.C -- Digital Clock (c) Charles Petzold, 1998 ----- ...

  7. Maven实战(十)利用 Nexus 来构建企业级 Maven 仓库

    目录 一.简介 Nexus是Maven仓库管理器,用来搭建一个本地仓库服务器,这样做的好处是便于管理,节省网络资源,速度快,还有一个非常有用的功能就是可以通过项目的SNAPSHOT版本管理,来进行模块 ...

  8. 智能指针shared_ptr新特性shared_from_this及weak_ptr

    enable_shared_from_this是一个模板类,定义于头文件<memory>,其原型为: template< class T > class enable_shar ...

  9. spring中MessageSource的配置使用方法2--ReloadableResourceBundleMessageSource【转】

    本文转载仅供自己学习收录,不做任何商业用途,如有需要可访问原地址:http://blog.csdn.net/qyf_5445/article/details/8124362 如何在spring mvc ...

  10. JavaScript创建对象的6种方式

    JavaScript创建对象简单的说,无非就是使用内置对象(Object)或各种自定义对象,当然还可以用JSON,但写法有很多种,也能混合使用. 1.对象字面量的方式 person = {name : ...