转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 软件开发 >> Delphi程序 >> 正文
Delphi下的COM编程         ★★★★

Delphi下的COM编程

作者:闵涛 文章来源:闵涛的学习笔记 点击数:1661 更新时间:2009/4/23 18:25:45

 
Delphi下的COM编程

作者:岑心 03/9(转载需征得作者同意)


Delphi通过向导可以非常迅速和方便的直接建立实现COM对象的代码,但是整个COM实现的过程被完全的封装,甚至没有VCL那么结构清晰可见。

一个没有C++下COM开发经验甚至没有接触过COM开发的Delphi程序员,也能够很容易的按照教程设计一个接口,但是,恐怕深入一想,连生成的

代码代表何种意义,哪些能够定制都不清楚。前几期 “DELPHI下的COM编程技术”一文已经初步介绍了COM的一些基本概念,我则想谈一些个人

的理解,希望能给对Delphi下COM编程有疑惑的朋友带来帮助。

COM (组件对象模型 Component Object Model)是一个很庞大的体系。简单来说,COM定义了一组API与一个二进制的标准,让来自不同平台、不

同开发语言的独立对象之间进行通信。COM对象只有方法和属性,并包含一个或多个接口。这些接口实现了COM对象的功能,通过调用注册的COM

对象的接口,能够在不同平台间传递数据。

COM光标准和细节就可以出几本大书。这里避重就轻,仅仅初步的解释Delphi如何进行COM的封装及实现。对于上述COM技术经验不足的Delphi程

序开发者来说,Delphi通过模版生成的代码就像是给你一幅抽象画照着画一样,画出来了却不一定知道画的究竟是什么,也不知该如何下手画

自己的东西。本文能够帮助你解决这类疑惑。

再次讲解一些概念

“DELPHI下的COM编程技术”一文已经介绍了不少COM的概念,比如GUID、CLSID、IID,引用计数,IUnKnown接口等,下面再补充一些相关内容

COM与DCOM、COM+、OLE、ActiveX的关系

DCOM(分布式COM)提供一种网络上访问其他机器的手段,是COM的网络化扩展,可以远程创建及调用。COM+是Microsoft对COM进行了重要的更

新后推出的技术,但它不简单等于COM的升级,COM+是向后兼容的,但在某些程度上具有和COM不同的特性,比如无状态的、事务控制、安全控

制等等。

以前的OLE是用来描述建立在COM体系结构基础上的一整套技术,现在OLE仅仅是指与对象连接及嵌入有关的技术;ActiveX则用来描述建立在COM

基础上的非COM技术,它的重要内容是自动化(Automation),自动化允许一个应用程序(称为自动化控制器)操纵另一个应用程序或库(称为

自动化服务器)的对象,或者把应用程序元素暴露出来。

由此可见COM与以上的几种技术的关系,并且它们都是为了让对象能够跨开发工具跨平台甚至跨网络的被使用。


Delphi下的接口

Delphi中的接口概念类似C++中的纯虚类,又由于Delphi的类是单继承模式(C++是多继承的),即一个类只能有一个父类。接口在某种程度上

可以实现多继承。接口类的声明与一般类声明的不同是,它可以象多重继承那样,类名 = class (接口类1,接口类2… ),然后被声明的接口

类则重载继承类的虚方法,来实现接口的功能。

以下是IInterface、IUnknown、IDispatch的声明,大家看出这几个重要接口之间是什么样的联系了吗?任何一个COM对象的接口,最终都是从

IUnknown继承的,而Automation对象,则还要包含IDispatch,后面DCOM部分我们会看到它的作用。

  IInterface = interface

    [''''{00000000-0000-0000-C000-000000000046}'''']

    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

    function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

  end;

  IUnknown = IInterface;

  IDispatch = interface(IUnknown)

    [''''{00020400-0000-0000-C000-000000000046}'''']

    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;

    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;

    function GetIDsOfNames(const IID: TGUID; Names: Pointer;

      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;

    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;

      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;

  end;

对照“DELPHI下的COM编程技术”一文,可以明白IInterface中的定义,即接口查询及引用记数,这也是访问和调用一个接口所必须的。

QueryInterface可以得到接口句柄,而AddRef与Release则负责登记调用次数。

COM和接口的关系又是什么呢?COM通过接口进行组件、应用程序、客户和服务器之间的通信。COM对象需要注册,而一个GUID则是作为识别接口

的唯一名字。

假如你创建了一个COM对象,它的声明类似 Txxxx= class(TComObject, Ixxxx),前面是COM对象的基类,后面这个接口的声明则是:Ixxxx =

interface(IUnknown)。所以说IUnknown是Delphi中COM对象接口类的祖先。到这一步,我想大家对接口类的来历已经有初步了解了。


聚合

接口是COM实现的基础,接口也是可继承的,但是接口并没有实现自己,仅仅只有声明。那么怎么使COM对象对接口的实现得到重用呢?答案就

是聚合。聚合就是一个包含对象(外部对象)创建一个被包含对象(内部对象),这样内部对象的接口就暴露给外部对象。

简单来说,COM对象被注册后,可以找到并调用接口。但接口不是仅仅有个定义吗,它必然通过某种方式找到这个定义的实现,即接口的“实现

类”的方法,这样才最终通过外部的接口转入进行具体的操作,并通过接口返回执行结果。


进程内与进程外(In-Process, Out-Process)

进程内的接口的实现基础是一个DLL,进程外的接口则是建立在应用程序(EXE)上的。通常我们建立进程外接口的目的主要是为了方便调试(

跟踪DLL是件很麻烦的事),然后在将代码改为进程内发布。因为进程内比进程外的执行效率会高一些。

COM对象创建在服务器的进程空间。如果是EXE型服务器,那么服务器和客户端不在同一进程;如果是DLL型服务器,则服务器和客户端就是一个

进程。所以进程内还能节省内存空间,并且减少创建实例的时间。

StdCall与SafeCall

Delphi生成的COM接口默认的方法函数调用方式是stdcall而不是缺省的Register。这是为了保证不同语言编译器的接口兼容。

双重接口(在后面讲解自动化时会提到双重接口)中则默认的是SafeCall。它的意义除了按SafeCall约定方式调用外,还将封装方法以便向调

用者返回HResult值。SafeCall的好处是能够捕获所有异常,即使是方法中未被代码处理的异常,也可以被外套处理并通过HResult返回给调用

者。


WideString等一些有差异的类型

接口定义中缺省的字符参数或返回值将不再是String而是WideString。WideString 是Delphi中符合OLE 32-bit版本的Unicode类型,当是字符

时,WideString与String几乎等同,当处理Unicode字符时,则会有很大差别。联想到COM本身是为了跨平台使用,可以很容易的理解为什么数

据通信时需要使用WideString类型。

同样的道理,integer类型将变成SYSINT或者Int64、SmallInt或者Shortint,这些细微的变化都是为了符合规范。

通过向导生成基础代码


打开创建新工程向导(菜单“File-New-Other”或“New Items按钮”),选择ActiveX页。先建立一个ActiveX Library。编译后即是个DLL文

件(进程内)。然后在同样的页面再建立一个COM Object。


实例模式与线程模式

接着你将看到如下向导,除了填写类名外(接口名会自动根据类名填充),还有实例创建方式(Instancing)和线程模式(Threading Model)

的选项。


实例模式决定客户端请求后,COM对象如何创建实例:

Internal:供COM对象内部使用,不会响应客户端请求,只能通过COM对象内部的其他

方法来建立;

Single Instance:不论当前系统内部是否存在相同COM对象,都会建立一个新的程序

及独立的对象实例;

Mulitple Instance:如果有多个相同的COM对象,只会建立一个程序,多个COM对象

的实例共享公共代码,并拥有自己的数据空间。

Single/ Mulitple Instance有各自的优点,Mulitple虽然节省了内存但更加费时。即Single模式需要更多的内存资源,而Mulitple模式需要更

多的CPU资源,且Single的实例响应请求的负荷较为平均。该参数应根据服务器的实际需求来考虑。


线程模式有五种:

Single:仅单线程,处理简单,吞吐量最低;

Apartment:COM程序多线程,COM对象处理请求单线程;

Free:一个COM对象的多个实例可以同时运行。吞吐量提高的同时,也要求对COM对象

进行必要的保护,以避免多个实例冲突;

Both:同时支持Aartment和Free两种线程模式。

Neutral:只能在COM+下使用。

虽然Free和Both的效率得到提高,但是要求较高的技巧以避免冲突(这是很不容易调试的),所以一般建议使用Delphi的缺省方式。


类型库编辑器(Type Library)

假设我们建立一个叫做TSample的类和ISample的接口(如图),然后使用类型库编辑器创建一个方法GetCOMInfo(在右边树部分点击右键弹出

菜单选择New-Method或者点击上方按钮),并于左边Parameters页面建立两个参数(ValInt : Integer , ValStr : String),返回值为BSTR

。如图:


可以看到,除了常用类型外,参数和返回值还可以支持很多指针、OLE对象、接口类型。建立普通的COM对象,其Returen Type是可以任意的,

这是和DCOM的一个区别。

双击Modifier列弹出窗口,可以选择参数的方式:in、out分别对应const、out定义,选择Has Default Value可设置参数缺省值。


Delphi生成代码详解


点击刷新按钮刷新后,上面类型库编辑器对应的Delphi自动生成的代码如下:

unit uCOM;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses

  Windows, ActiveX, Classes, ComObj, pCOM_TLB, StdVcl;

type

  TSample = class(TTypedComObject, ISample)

  protected

    function GetCOMInfo(ValInt: SYSINT; const ValStr: WideString): WideString;

      stdcall;

  end;

implementation

uses ComServ;

function TSample.GetCOMInfo(ValInt: SYSINT;const ValStr: WideString): WideString;

begin

end;

initialization

  TTypedComObjectFactory.Create(ComServer, TSample, Class_Sample,

    ciMultiInstance, tmApartment);

end.

引用单元

有三个特殊的单元被引用:ComObj,ComServ和pCOM_TLB。ComObj里定义了COM接口类的父类TTypedComObject和类工厂类

TTypedComObjectFactory(分别从TComObject和TComObjectFactory继承,早期版本如Delphi4建立的COM,就直接从TcomObject继承和使用

TComObjectFactory了); ComServ单元里面定义了全局变量ComServer: TComServer,它是从TComServerObject继承的,关于这个变量的作用

,后面将会提到。

这几个类都是delphi实现COM对象的比较基础的类,TComObject(COM对象类)和TComObjectFactory(COM对象类工厂类)本身就是IUnknown的

两个实现类,包含了一个COM对象的建立、查询、登记、注册等方面的代码。TComServerObject则用来注册一个COM对象的服务信息。

接口定义说明

再看接口类定义TSample = class(TTypedComObject, ISample)。到这里,已经可以通过涉及的父类的作用大致猜测到TSample是如何创建并注

册为一个标准的COM对象的了。那么接口ISample又是怎么来的呢?pCOM_TLB单元是系统自动建立的,其名称加上了_TLB,它里面包含了ISample

= interface(IUnknown)的接口定义。前面提到过,所有COM接口都是从IUnknown继承的。

在这个单元里我们还可以看到三种ID(类型库ID、IID及COM注册所必须的CLSID)的定义:LIBID_pCOM,IID_ISample和CLASS_Sample。关键是

这时接口本身仅仅只有定义代码而没有任何的实现代码,那接口创建又是在何处执行的?_TLB单元里还有这样的代码:

CoSample = class

  class function Create: ISample;

  class function CreateRemote(const MachineName: string): ISample;

end;

class function CoSample.Create: ISample;

begin

  Result := CreateComObject(CLASS_Sample) as ISample;

end;

class function CoSample.CreateRemote(const MachineName: string): ISample;

begin

  Result := CreateRemoteComObject(MachineName, CLASS_Sample) as ISample;

end;

由Delphi的向导和类型编辑器帮助生成的接口定义代码,都会绑定一个“Co+类名”的类,它实现了创建接口实例的代码。CreateComObject和

CreateRemoteComObject函数在ComObj单元定义,它们就是使用CLSID创建COM/DCOM对象的函数!

初始化:注册COM对象的类工厂

类工厂负责接口类的统一管理——实际上是由支持IClassFactory接口的对象来管理的。类工厂类的继承关系如下:

IClassFactory = interface(IUnknown)

TComObjectFactory=class(TObject,IUnknown,IClassFactory,IClassFactory2) TTypedComObjectFactory = class(TComObjectFactory)

我们知道了接口ISample是怎样被创建的,接口实现类TSample又是如何被定义为COM对象的实现类。现在解释它是怎么被注册,以及何时创建的

。这一切的小把戏都在最后initialization的部分,这里有一条类工厂建立的语句。

Initialization是Delphi用于初始化的特殊部分,此部分的代码将在整个程序启动的时候首先执行。回顾前面的内容并观察一下

TTypedComObjectFactory的参数:ComServer是用于注册/撤消注册COM服务的对象,TSample是接口实现类,Class_Sample是接口唯一对应的

GUID,ciMultiInstance是实例模式,tmApartment是线程模式。一个COM对象应该具备的特征和要素都包含在了里面!

那么COM对象的管理又是怎么实现的呢?在ComObj单元里面可以见到一条定义function ComClassManager: TComClassManager;

这里TComClassManager顾名思义就是COM对象的管理类。任何一个祖先类为TComObjectFactory的对象被建立时,其Create里面会执行这样一句

ComClassManager.AddObjectFactory(Self);

AddObjectFactory方法的原形为procedure TComClassManager.AddObjectFactory(Factory: TComObjectFactory);相对应的还有

RemoveObjectFactory方法。具体的代码我就不贴出来了,相信大家已经猜测到了它的作用——将当前对象(self)加入到ComClassManager管

理的对象链(FFactoryList)中。

封装的秘密

读者应该还有最后一个疑问:假如服务器通过类工厂的注册以及GUID确定一个COM对象,那当客户端调用的时候,服务器是如何启动包含COM对

象的程序的呢?

当你建立ActiveX Library的工程的时候,将发现一个和普通DLL模版不同的地方——它定义了四个输出例程:

exports

  DllGetClassObject,

  DllCanUnloadNow,

  DllRegisterServer,

  DllUnregisterServer;

这四个例程并不是我们编写的,它们都在ComServ单元例实现。单元还定义了类TComServer,并且在初始化部分创建了类的实例,即前面提到过

的全局变量ComServer。

例程DllGetClassObject通过CLSID得到支

[1] [2]  下一页


[聊天工具]Google Suggest十大妙用  [聊天工具]保驾护航Web迅雷 全新版本给你更多安全
[聊天工具]玩转火狐的Cookie 让火狐狸吃好小甜饼!  [聊天工具]P2P下载的好工具 POCO完全攻略
[聊天工具]横扫一切高价话费 Vbuzzer八分钱国际长途任你打  [聊天工具]众人拾柴火焰高 改进迅雷于不经意间 迅雷
[聊天工具]中英文互翻 Google Toolbar4中文版试用手记  [聊天工具]巧用µTorrent 体验国外下载站的乐趣
[聊天工具]可远程搜索桌面—Google Desktop 3全新体验  [聊天工具]腾讯浏览器 TT 之实用技巧荟萃 TT,技巧荟萃
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · C语言系列  · VB.NET程序
    · JAVA开发  · Delphi程序
    · 脚本语言
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台