分享

com程序编写入门(全文-1)

 frie 2005-08-08
 

COM程序编写入门

日期:2004-5-2

名词解释:

OLE: Object linking and embedding对象的链接与嵌入)

       使得服务器模块和客户模块通过标准的接口进行通讯。两个模块可以在同一台计算机或位于不同的计算机上,位置对用户来说是无关紧要的。服务器模块实现了一组接口,客户模块通过这组接口进行通讯。

COM: Component object modal组件对象模型)

       实现了OLE的功能,具体可完成一下功能:

l        编写供多种语言使用的代码;

多种语言:指的是建立好的COM组件不在乎访问它的编程语言,任何一种编程语言只要知道COM组件的接口,访问是都能完成同样的功能。

l        创建ActiveX控件;

l        通过OLE Automation操纵其他应用程序;

如:Microsoft ExcelOLE编程接口,创建对象后,任何一种程序都可以实现对Excel的操作。

l        与其他计算机上的应用程序通讯;

实际为COM接口与接口之间的通讯,因其实现了不同语言、不同计算机的方式,所以实现不同计算机上应用程序的通讯也就十分容易。

COM模块

COM的模块指独立的应用程序(EXE)或者动态连接库(DLL),在实现COM时,采用DLL方式要比较容易一些。因为:应用程序在加载时在内存中都是独立的地址控件,而DLL加载后可以驻留内存。当多个客户端调用COM时,如果采用EXE形式,就会有多个EXE被加载,而且COM处理客户端的访问时,也必须在不同的地址空间来回切换,大费周张。而DLL形式永远只有一个驻留内存,COM只要在相同的内存空间中寻找执行代码即可。

逐步深入:

DLL我们都已经写了很多,通常我们写的DLL都是定义的一些方法或是过程来实现特定的操作,当然定义的出口(Exports)也就是这些方法或是过程。现在我们来写一个出口为一个类的DLL来展开我们COM编写的学习。

准备工作:

打开Delphi,选择File\New\Others,选择DLL Wizard自动创建一个DLL工程,选择File\New\Unit新建一个单元,全部保存。

写代码:

在新建的Unit中定义一个抽象类:

Type

  TCalculator=Class

    Public

      Function Addition (Op1, Op2: Double): Double ; virtual; abstract;

  End;

这个类很简单,就定义了一个抽象方法实现两个数的相加。

定义抽象类后,定义一个类来实现这个抽象类:

Type

  TCalcImple=Class (TCalculator)

  Public

    Function Addition (Op1, Op2: Double): Double; Override;

  End;

方法实现:

Function TCalcImple.Addition(Op1, Op2: Double): Double;

Begin

  Result: =Op1+Op2;

End;

当然我们还差一个创建这个类的方法,我们在加入如下:

Function CreateCalcImple:TCalcImple;stdcall;

Begin

  Result:=TCalcImple.Create;

End;

定义出口:

Exports CreateCalcImple;

这样我们这个DLL的编写就完成了,我问再来写一个Exe程序来调用它。具体在此就不再操作以便,一些列出Exe的源代码:

unit Unit1;

interface

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, StdCtrls;

{定义与DLL中定义的抽象类一样的类,类名称可以自定}

Type

  TCalculator=Class

  Public

    Function Addition(Op1,Op2:Double):Double;virtual;abstract;

  End;

type

  TForm1 = class (TForm)

    Button1: TButton;

    Edit1: TEdit;

    Edit2: TEdit;

    Edit3: TEdit;

    Label1: TLabel;

    Label2: TLabel;

    Label3: TLabel;

    procedure Button1Click(Sender: TObject);

  private

    { Private declarations }

  public

    { Public declarations }

    v_Obj:TCalculator;

  end;

{静态调用DLL,注意返回类型与DLL中的不同,为抽象类的名称必须与DLL中的一致}

  Function CreateCalcImple:TCalculator;stdcall;External ‘ComDLL.dll‘;

var

  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);

begin

  v_Obj:=CreateCalcImple;

  Edit3.Text:=FloatToStr(v_Obj.Addition(StrToFloat(Edit1.Text),StrToFloat(Edit2.Text)));

  v_Obj.Free;

end;

 

end.

升华到理论:

l        D L L中定义的对象只能引出抽象方法。当建立对象时, D L L向应用程序返回虚拟方法的指针表VTable

l        在定义抽象类的时候,定义的方法为:

Function Addition(Op1,Op2:Double):Double;virtual;abstract;

后面加上abstract(抽象方法表示)的原因是因为只有抽象方法才能被引出。

l        在执行文件中,通过DLL的出口实际只创建了一个接口,但是可以像使用对象一样来调用其方法,这就有点开始尽是于COM了。

 

有上面几点引出:

1、COM的接口可以看成一个占位符,具体的实现是在接口对应的类中;就像我们定义的抽象类(TCalculator)中的Addition方法,只是一个没有任何意义的描述符,但通过实现类(TCalcImple)中引出后便有了具体的意义;

2、COM接口的访问必须通过其接口类进入后才能够访问。就如我们例子中加入CreateCalcImple方法一样,只有建立这个接口类(Interface)后才可以访问具体的接口。

3、也就是说,一个COM必须具备三个方面的元素:接口定义类、接口实现类、接口创建类。只要具备了这三个方面的元素就可以实现COM

 

COM的理论

以实例来讲

COM的接口(Interface)是COM的核心,所有的COM接口都是通过IUnknown派生出来的,它告知客户那些接口是有效的,即已经被实现类说定义。它定义的一般方式如下:

ISimpleInterface=Interface(IUnknown)

       Function GetName:String

       Procedure SetName(v_Name:String)

       End;

如果在上面的接口中加入这样一行:

ISimpleInterface=Interface(IUnknown)

       V_Name:String;

       Function GetName:String

       Procedure SetName(v_Name:String)

       End;

这样是不被允许的,因为上面我们说到接口方法就像是一个占位符,需要实现类引出才有实际意义,v_Name:String这一句只是一个数据成员将永远无任何意义,如果要定义也只能在实现类中定义。

现在举一个COM的例子,没有什么实际用处但至少说明问题:

unit Unit1;

 

interface

 

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, StdCtrls;

 

type

  TForm1 = class(TForm)

    Label1: TLabel;

    Edit1: TEdit;

    Button1: TButton;

    Button2: TButton;

    procedure FormCreate(Sender: TObject);

    procedure Button1Click(Sender: TObject);

    procedure Button2Click(Sender: TObject);

    procedure FormClose(Sender: TObject; var Action: TCloseAction);

  private

    { Private declarations }

  public

    { Public declarations }

  end;

 

  ISimpleInterface=Interface(IUnknown)

    Procedure SetValue(v_Value:Integer);

    Function GetValue:Integer;

  End;

 

  TSimpleImple=Class(TInterfacedObject,ISimpleInterface)

  Public

    Value:Integer;

    Procedure SetValue(v_Value:Integer);

    Function GetValue:Integer;

  End;

 

var

  Form1: TForm1;

  v_Obj:TSimpleImple;

implementation

 

{$R *.dfm}

 

{ TSimpleImple }

 

function TSimpleImple.GetValue: Integer;

begin

  Result:=Value;

end;

 

procedure TSimpleImple.SetValue(v_Value: Integer);

begin

  Value:=v_Value;

end;

 

procedure TForm1.FormCreate(Sender: TObject);

begin

  v_Obj:=TSimpleImple.Create;

end;

 

procedure TForm1.Button1Click(Sender: TObject);

begin

  v_Obj.SetValue(StrToInt(Edit1.Text));

  Edit1.Clear;

end;

 

procedure TForm1.Button2Click(Sender: TObject);

begin

  Edit1.Text:=IntToStr(v_Obj.GetValue);

end;

 

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);

begin

  v_Obj.Free;

end;

 

end.

蓝色字样即定义了一个接口,在形式上在ISimpleInterface(接口定义)TSimpleImple(实现类)几乎定义都差不多,但是我要强调的是,接口定义是为了实现OLE方式的访问,而实现类的定义,是接口功能的实现。两者在功能和实现上都是有区别的。

 

COM对象的生存周期与IUnknown接口

COM对象的生存周期分为两部分来讲:客户端与COM本身:

在客户端,视定义的COM对象接口而定,像我们例子中的v_Obj,定义成全局变量,那么COM对象在创建时产生,只有在程序退出时才被释放。我们也可以在形式上将其释放,如:v_Obj:=nil,这样这个COM接口就无效了。

COM本身,COM接口的通过记数的方式来完成COM的生存周期,为什么采用记数,当然很简单——因为COM可能同时被多个程序所调用。有一个程序连接到COM时计数器加1,某个释放时计数器减1,当计数器为0时,COM对象才真正从内存中移除。

IUnknown接口:

       为什么将IUnknown接口与生存周期放在一起讲是有原因的,COM生存周期中的计数器就定义在IUnknown接口中:AddRefReleaseQueryInterface。这三个接口也是IUnknown的全部身家。对三个接口还是解释一下:

AddRef:当COM产生一个客户端连接的时候,AddRef方法负责将计数器加1

Release:当COM释放一个客户端连接的时候,Release方法负责将计数器减1,如果计数器为0,释放COM

QueryInterface:因COM支持多个接口,QueryInterface负责找出用户指定的接口以返回正确的VTable

接口全局标识:

       上面说到QueryInterface的时候,提到了要找到正确的接口。其实正确的接口就是靠全局标识符来识别的。它是一个128位的数字,是按照统计学的方法,计算出来的,可唯一标识出每个接口(理论上)。具体实现我们不用管,它产生的方法很简单,在Delphi中按CtrlShiftG就可以产生一个。

COM实现在Delphi中的实现

相信通过上面的介绍对COM应该也有了初步的认识了,现在就将点实际的东西,如何在Delphi下编写COM

       Delphi下面开发COM是比较容易的,Delphi封装的COM开发的最基本的要素,只要你去编写对象的实现类就行了,其他的全有Delphi搞定。

1、打开Delphi,选择File\New\Others,选择Active页的ActiveX Library,选择File\New\Others,选择Active页的COM Object,出现的向导中比较重要的选项如下:

Class Name:实现类的名称,自定。

Include Type Library:是否包含类型库文件,如果不选择,Delphi将不产生类型库文件,应此上面输入的Class Name也无效。也就意味着接口类、实现类、实现方法都的自己写。对于不是很熟悉COM的的人员最好不要采用这种方式。

其它参数均可采用默认值,具体意义可参见有关资料。

2、接口的编写

选择View\Type Library,选择接口,右键New选择Method,在右边AttributesName中输入接口的名称,在Parameters中加入需要加入的输入和输出参数。注意:设置参数类型时,如果是返回参数的,参数类型后面要加上“*”。点击刷新,在程序单元中就出现了刚定义的接口,在此编写实现代码就可以了。

3、COM的安装

编写完成后编译,通过Run\Register ActiveX Server注册编译好的COM,通过Run\Install COM+ Objects安装COM组件,在弹出的对话框中选择接口,在接下来的对话框中可以选择安装到已有的COM应用程序中也可以安装到新的COM应用程序中。这样就完成了COM的安装,你可以打开系统中的组件服务看到你所安装的COM

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多