博文

COM组件设计与应用(十二)(2006-02-23 16:33:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1520 错误与异常处理

作者:杨老师 下载源代码

一、前言
  程序设计中,错误处理必不可少,而且通常要占用很大的篇幅。本回书着落在 COM 中的错误(异常)的处理方法。
  在组件程序中,如果遇到错误,一般有两个方式进行处理。

二、简单返回
  对于比较简单的错误,直接返回表示错误原因的 HRESULT。比如下面几个就是常见的错误值:
  E_INVALIDARG 0x80070057 参数错误 E_OUTOFMEMORY 0x8007000E 内存错误 E_NOTIMPL 0x80004001 未实现 E_POINTER 0x80004003 无效指针 E_HANDLE 0x80070006 无效句柄 E_ABORT 0x80004004 终止操作 E_ACCESSDENIED 0x80070005 拒绝访问 E_NOINTERFACE 0x80004002 不支持接口   另外,你还可以返回自己构造 HRESULT 错误值。方法是使用宏 MAKE_HRESULT(sev,fac,code)
  参数 含义 值(二进制) sev 严重程度 成功 00 成功,但有一些报告信息 01 警告 10 错误 11 fac 设备信息 FACILITY_AAF 00000010010 FACILITY_ACS 00000010100 FACILITY_BACKGROUNDCOPY 00000100000 FACILITY_CERT 00000001011 FACILITY_COMPLUS 00000010001 FACILITY_CONFIGURATION 00000100001 FACILITY_CONTROL 00000001010 FACILITY_DISPATCH 00000000010 FACILITY_DPLAY 00000010101 FACILITY_HTT......

阅读全文(3099) | 评论:0

COM 组件设计与应用(十一)(2006-02-23 16:32:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1518 IDispatch 及双接口的调用

作者:杨老师 下载源代码

一、前言
    前段时间,由于工作比较忙,没有能及时地写作。其间收到了很多网友的来信询问和鼓励,在此一并表示感谢。咳......我也需要工作来养家糊口呀......
    上回书介绍了两种方法来写自动化(IDispatch)接口的组件程序,一是用 MFC 方式编写“纯粹”的 IDispatch 接口;二是用 ATL 方式编写“双接口”的组件。 二、IDispatch 接口和双接口
    使用者要想调用普通的 COM 组件功能,必须要加载这个组件的类型库(Type library)文件 tlb(比如在 VC 中使用 #import)。然而,在脚本程序中,由于脚本是被解释执行的,所以无法使用加载类型库的方式进行预编译。那么脚本解释器如何使用 COM 组件那?这就是自动化(IDispatch)组件大显身手的地方了。IDispatch 接口需要实现4个函数,调用者只通过这4个函数,就能实现调用自动化组件中所有的函数。这4个函数功能如下:
  HRESULT GetTypeInfoCount(
    [out] UINT * pctinfo) 组件中提供几个类型库?当然一般都是一个啦。
但如果你在一个组件中实现了多个 IDispatch 接口,那就不一定啦(注1) HRESULT GetTypeInfo(
    [in] UINT iTInfo,
    [in] LCID lcid,
    [out] ITypeInfo ** ppTInfo) 调用者通过该函数取得他想要的类型库。
幸好,在 99% 的情况下,我们都不用关心这两个函数的实现,因为 MFC/ATL 都帮我们完成了默认的一个实现,如果是自己完成函数代码,甚至可以直接返回 E_NOTIMPL 表示没有实现。(......

阅读全文(2999) | 评论:0

COM组件设计与应用(十)(2006-02-21 20:15:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1507 IDispatch 接口 for vc.net

作者:杨老师 下载源代码 一、前言
    终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性。
    如果你使用 vc6.0 的开发环境,请阅读前一回。
    如果你使用 vc.net 2003,请继续...... 二、IDispatch接口
    如果是编译型语言,那么我们可以让编译器在编译的时候装载类型库,也就是装载接口的描述。在第七回文章当中,我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后,编译器就知道应该如何编译接口函数的调用了---这叫“前绑定”。但是,如果想在脚本语言中使用组件,问题就大了,因为脚本语言是解释执行的,它执行的时候不会知道具体的函数地址,怎么办?自动化接口就为此诞生了---“后绑定”。
    自动化组件,其实就是实现了 IDispatch 接口的组件。IDispatch 接口有4个函数,解释语言的执行器就通过这仅有的4个函数来执行组件所提供的功能。IDispatch 接口用 IDL 形式说明如下:(注1)[ object, uuid(00020400-0000-0000-C000-000000000046), // IDispatch 接口的 IID = IID_IDispatch pointer_default(unique) ] interface IDispatch : IUnknown { typedef [unique] IDispatch * LPDISPATCH; // ......

阅读全文(2969) | 评论:0

COM组件设计与应用(九)(2006-02-21 20:14:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1506 IDispatch 接口 for vc6.0

作者:杨老师 下载源代码 一、前言
    终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用、非常有用、非常精彩的一个 COM 功能。由于 WORD、EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML、ASP、JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性。
    如果你使用 vc6.0 的开发环境,请继续阅读。
    如果你使用 vc.net 2003,请阅读下一回。 二、IDispatch接口
    如果是编译型语言,那么我们可以让编译器在编译的时候装载类型库,也就是装载接口的描述。在第七回文章当中,我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后,编译器就知道应该如何编译接口函数的调用了---这叫“前绑定”。但是,如果想在脚本语言中使用组件,问题就大了,因为脚本语言是解释执行的,它执行的时候不会知道具体的函数地址,怎么办?自动化接口就为此诞生了---“后绑定”。
    自动化组件,其实就是实现了 IDispatch 接口的组件。IDispatch 接口有4个函数,解释语言的执行器就通过这仅有的4个函数来执行组件所提供的功能。IDispatch 接口用 IDL 形式说明如下:(注1)[ object, uuid(00020400-0000-0000-C000-000000000046), // IDispatch 接口的 IID = IID_IDispatch pointer_default(unique) ] interface IDispatch : IUnknown { typedef [unique] IDispatch * LPDISPATCH; // 转定义 ......

阅读全文(2565) | 评论:0

COM组件设计与应用(八)(2006-02-21 20:13:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1501 实现多接口

作者:杨老师 下载源代码 一、前言
    从第五回开始到第七回,咱们用 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 的新接口功能。看,多平顺的升级方式呀!

三、实现
&n......

阅读全文(2187) | 评论:0

COM 组件设计与应用(七)(2006-02-20 12:28:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1500 编译、注册、调用

作者:杨老师 一、前言

  上两回中,咱们用 ATL 写了第一个 COM 组件程序,这回中,主要介绍编译、注册和调用方法。示例程序你已经下载了吗?如果还没有下载,vc6.0 的用户点这里,vc.net 的用户点这里。


二、关于编译

  2-1 最小依赖
  “最小依赖”,表示编译器会把 ATL 中必须使用的一些函数静态连接到目标程序中。这样目标文件尺寸会稍大,但独立性更强,安装方便;反之系统执行的时候需要有 ATL.DLL 文件的支持。如何选择设置为“最小依赖”呢?答案是:删除预定义宏“_ATL_DLL”,操作方法见图一、图二。


图一、在vc6.0中,设置方法


图二、在 vc.net 2003中,设置方法

  2-2 CRT库
  如果在 ATL 组件程序中调用了 CRT 的运行时刻库函数,比如开平方 sqrt() ,那么编译的时候可能会报错“error LNK2001: unresolved external symbol _main”。怎么办?删除预定义宏“_ATL_MIN_CRT”!操作方法也见图一、图二。(vc.net 2003 中的这个项目属性叫“在 ATL 中最小使用 CRT”)

  2-3 MBCS/UNICODE
  这个不多说了,在预定义宏中,分别使用 _MBCS 或 _UNICODE。

  2-4 IDL 的编译
  COM 在设计初期,就定了一个目标:要能实现跨语言的调用。既然是跨语言的,那么组件的接口描述就必须在任何语言环境中都要能够认识。怎么办?用 .h 文件描述?------ C语言程序员笑了,真方便!BASIC 程序员哭了:-( 因此,微软使用了一个新的文件格式---IDL文件(接口定义描述语言)。IDL 是一个文本文件,它的语言语法比较简单,很象C。具体 IDL 文件的讲解,见下一回《COM 组件设计与应用(八)之添加新接口》。IDL 经过编译,生成二进制的等价类型库文件 TLB 提供给其它语言来使用。图三示意了 ATL COM 程序编......

阅读全文(2694) | 评论:0

COM 组件设计与应用(六)(2006-02-20 12:27:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1498 用 ATL 写第一个组件

作者:杨老师 下载源代码

一、前言

  1、与 《COM 组件设计与应用(五)》的内容基本一致。但本回讲解的是在 vc.net 2003 下的使用方法,即使你不再使用vc6.0,也请和上一回的内容,参照比对。
  2、这第一个组件,除了所有 COM 组件必须的 IUnknown 接口外,我们再实现一个自己定义的接口 IFun,它有两个函数: Add()完成两个数值的加法,Cat()完成两个字符串的连接。
  3、下面......好好听讲! 开始了:-)
  二、建立 ATL 工程

  步骤2.1:建立一个解决方案。
  步骤2.2:在 该解决方案中,新建一个 vc++ 的 ATL 项目。示例程序叫 Simple2,并选择DLL方式,见图一、图二。


图一、新建 ATL 项目


图二、选择非属性化的DLL组件类型

  属性化 属性化编程,是未来的方向,但我们现在先不要选它。
  动态链接库(DLL) 选择它。
  可执行文件(EXE) 以后再讲。
  服务(EXE) 表示建立一个系统服务组件程序,系统启动后就会加载并执行的程序。
  允许合并代理/存根(stub)代码 选择该项表示把“代理/存根”代码合并到组件程序中,否则需要单独编译,单独注册代理存根程序。代理/存根,这个是什么概念?还记得我们在上回书中介绍的吗?当调用者调用进程外或远程组件功能的时候,其实是代理/存根负责数据交换的。关于代理/存根的具体变成和操作,以后再说啦......
  支持 MFC 除非有特殊的原因,我们写 ATL 程序,最好不要选择该项。你可能会说,如果没有MFC的支持,那CString怎么办呀?告诉你个秘密吧,一般人我都不告诉他,我后半辈子就靠着这个秘密活着了:
  1、你会STL吗?可以用 STL 中的 string 代替;
  2、自己写个 MyString 类,嘿嘿;
  3、悄悄地、秘密地、不要告诉别人(特别是别告诉微软),把 MFC 中的 CString 源码拿......

阅读全文(2805) | 评论:0

COM 组件设计与应用(五)(2006-02-20 12:26:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1497 用 ATL 写第一个组件

作者:杨老师 下载源代码

一、前言

  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()完成两个数值的加法,Cat()完成两个字符串的连接。
  5、下面......好好听讲! 开始了:-)
二、建立 ATL 工程
  步骤2.1:建立一个工作区(WorkSpace)。
  步骤2.2:在工作区中,建立一个 ATL 工程(Project)。示例程序叫 Simple1,并选择DLL方式,见图一。


图一、建立 ATL DLL 工程

  Dynamic Link Library(DLL) 表示建立一个 DLL 的组件程序。
  Executable(EXE) 表示建立一个 EXE 的组件程序。
  Service(EXE) 表示建立一个服务程序,系统启动后就会加载并执行的程序。
  Allow merging of proxy/stub code 选择该项表示把“代理/存根”代码合并到组件程序中,否则需要单独编译,单独注册代理存根程序。代理/存根,这个是什么概念?还记得我们在上回书中介绍的吗?当调用者调用进程外或远程组件功能的时候,其实是代理/存根负责数据交换的。关于代理/存根的具体变成和操作,以后再说啦......
  Support MFC 除非有特殊的原因,我们写 ATL 程序,最好不要选择该项。你可能会说,如果没有MFC的支持,那CString怎么办呀?告诉你个秘密吧,一般人我都不告诉他,我后半辈子就靠着这个秘密活着了:
  1、你会STL吗?可以用 STL 中的 string 代替;
  2、自己写个 MyStri......

阅读全文(2403) | 评论:0

COM组件设计与应用(四)(2006-02-19 12:03:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1493 简单调用组件

作者:杨老师 一、前言

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


二、组件的启动和释放

  在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码: p = new 对象; p->对象函数(); delete p;   这样的代码再熟悉不过了,在本地进程中运行是不会有问题的。但是你想想,如果这个对象是在“地球另一边”的计算机上,结果会如何?嘿嘿,C++ 在设计 new 的时候,可没有考虑远程的实现呀(计算机语言当然不会,也没必要去设计)。因此启动组件、调用接口的功能,当然就由 COM 系统来实现了。


图一 组件调用机制

  由上图可以看出,当调用组件的时候,其实是依靠代理(运行在本地)和存根(运行在远端)之间的通讯完成的。具体来说,当客户程序通过 CoCreateInstance() 函数启动组件,则代理接管该调用,它和存根通讯,存根则它所在的本地(相对于客户程序来说就是远程了)执行 new 操作加载对象。对于初学者,你可以不用理它,代理和存根对我们来说是透明的。只要大约知道是怎么一回事就一切OK了。
  问题又来了,这个远程的对象什么时候消灭呢?在第二回介绍接口概念的时候,当时我们特意忽略了两个函数,就是IUnknown::AddRef()和IUnknown::Release(),从函数名就能猜到了,一个是对内部引用记数器(Ref)加1,一个是释放(减1),当记数器减为0的时候,就是......

阅读全文(2605) | 评论:0

COM组件设计与应用(三)(2006-02-19 12:02:00)

摘要:原文出处:http://www.vckbase.com/document/viewdoc/?id=1488 数据类型

作者:杨老师 一、前言
  上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-)
  走入正题之前,请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!

二、HRESULT 函数返回值
  每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。
  函数 返回值 返回值信息 double sin(double) 浮点数值 计算正玄值 BOOL DeleteFile(LPCTSTR) 布尔值 文件删除是否成功。如失败,需要GetLastError()才能取得失败原因 void * malloc(size_t) 内存指针 内存申请,如果失败,返回空指针 NULL LONG RegDeleteKey(HKEY,LPCTSTR) 整数 删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因 UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT) 整数 取得拖放文件信息。以不同的参数调用,则返回不同的含义:
一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度 ......  ...... ... ......  ......   如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了......

阅读全文(2471) | 评论:0