打印本文 打印本文 关闭窗口 关闭窗口
Delphi中COM 实现研究手记(一)
作者:武汉SEO闵涛  文章来源:敏韬网  点击数1827  更新时间:2009/4/23 18:28:02  文章录入:mintao  责任编辑:mintao

前些日子用 Delphi 写了一个 Windows 外壳扩展程序,大家知道 Windows 外壳扩展实际上就是 COM 的一种应用 -- Shell COM,虽然整个程序写得还算比较顺利,但写完后还是感觉对 Delphi 中 COM 的实现有点雾里看花的感觉,因此我认为有必要花一点时间对 COM 在 Delphi 中的实现做一些研究。另外我也买了李维的新书 --《深入核心 -- VCL架构剖析》,里面有两章涉及了与 COM 相关内容,看完后我知道了COM 在 Delphi 中的实现是基于接口(Interface),而 Delphi 中的接口概念又起源于对 COM 的支持,总之他们之间互相影响,发展成接口在 Delphi 中已经是 First-Class 的地位,并且完全摆脱 COM 而独立存在。
    本系列文章侧重于描述 COM 在 Delphi 中的实现手法,主要配合 VCL 源码片断进行分析,不会涉及过多的基本概念,因此要求读者有一定的 COM 和 接口概念,可以参考我在文章末尾列出的文献。本篇主要讲 COM 对象在 Delphi 中的创建过程。

正文
   为了让读者能跟着我的分析轻松读完本篇文章,我引用文献[2]中的范例做解释,但为了更清楚地阐述问题,我改写了部分代码。所有分析请在 Delphi 7 上测试。Demo 源码这里
下载
   在
Delphi 中首先通过选择菜单 File-->New-->Other...新建一个 ActiveX Library 并保存名称为 SimpleComServer,再新建一个 COM Object,在COM Object Wizard 中将对象命名为 SimpleCOMObject,Options 中的两个复选框都可以不必选中其他的保持默认, 现在 COM服务器端的框架已经建立起来了。剩下的就是需要我们把声明的接口 ISimpleCOMObject 的代码实现,其他的读者自己看源码吧,很简单。
 

服务器端代码

library SimpleComServer;

uses
  ComServ,
  SimpleCOMObject in ''''SimpleCOMObject.pas''''
,
  SimpleComInterface in ''''SimpleComInterface.pas''''
,

exports
  DllGetClassObject,
  DllCanUnloadNow,
  DllRegisterServer,
  DllUnregisterServer;

{$R *.RES}

begin
end.


unit SimpleComInterface;

interface
uses
Windows;
const
  Class_SimpleComObject: TGUID = ''''{3714CF21-D272-11D3-947F-0050DA73BE5D}''''
;

type
  ISimpleComObject =
interface
    [''''{2E2A6DD0-D282-11D3-947F-0050DA73BE5D}''''
]
    function Multiply(X, Y: Integer): Integer; stdcall
;
    function GetClassName: Widestring; stdcall
;
  end
;

implementation

end


unit SimpleCOMObject;

interface

// SimpleCOMObject 的实现部分
uses
  Windows, ActiveX, Classes, ComObj, SimpleComInterface;

type
  TSimpleComObject = class
(TComObject, ISimpleComObject)
 
protected
    function Multiply(X, Y: Integer): Integer; stdcall;
    function GetClassName: Widestring; stdcall;

  end;

const
  Class_SimpleComObject: TGUID = ''''{3714CF21-D272-11D3-947F-0050DA73BE5D}''''
;

implementation

uses
ComServ;

{ TSimpleComObject }

function
TSimpleComObject.GetClassName: Widestring;
begin
  Result := TSimpleComObject.ClassName;
end
;

function
TSimpleComObject.Multiply(X, Y: Integer): Integer;
begin
  Result := X * Y;
end
;

initialization
  TComObjectFactory.Create(ComServer, TSimpleComObject, Class_SimpleComObject,
    ''''SimpleComObject'''', ''''A simple implementation of a COM Object''''
,
      ciMultiInstance, tmApartment);
end.


   完成服务器端的代码后,我们需要写一个客户端小程序来执行服务器端内的接口代码,我仅列出由我改写的关键代码部分,其他的见源码

客户端关键代码

procedure TForm1.Button1Click(Sender: TObject);
var
  aFactory: IClassFactory;
begin
  OleCheck(CoGetClassObject(Class_SimpleComObject, CLSCTX_INPROC_SERVER
or
    CLSCTX_LOCAL_SERVER, nil
, IClassFactory, aFactory));
  aFactory.CreateInstance(nil
, ISimpleComObject, ComInterface);
  ShowMessage(''''The result is: ''''
+
    IntToStr(ComInterface.Multiply(StrToInt(Edit1.Text), StrToInt(Edit2.Text))));
  ComInterface := nil
;
end
;

procedure
TForm1.Button2Click(Sender: TObject);
begin
  ComInterface := CreateComObject(Class_SimpleComObject) as
ISimpleComObject;
  ShowMessage(ComInterface.GetClassName);
  ComInterface := nil
;
end;


    现在开始进入主题,跟随我一起走进 Delphi 的 COM Framework 世界吧。我主要从客户端程序创建 COM 对象来剖析 VCL 源码。
   客户端代码中我用两种获得创建 SimpleCOMObject 对象并获得 ISimpleCOMObject 接口,一旦获得接口,你就可以自由地使用接口指定的方法了。
    让我们先看看 Button1Click 里如何创建 COM 对象的。代码调用了 CoGetClassObject 获得创建 SimpleCOMObject 对象的类工厂 -- IClassFactory 接口,紧接着又通过调用该接口的 CreateInstance 方法创建了真正的 SimpleCOMObject 对象实例,返回 ISimpleComObject 接口指针。 那么上面整个过程在 VCL 中是如何实现的呢?让我们先从 CoGetClassObject 这个API 说起。
    CoGetClassObject 是 Windows 的一个标准 COM API,该函数存在于 OLE32.DLL中,它是 Windows COM DLL 之一。函数先根据系统注册表中的信息,找到类标识符 CLSID 对应的组件程序(即服务器端程序,我们这里讨论的是一个 DLL 文件)的全路径,然后调用 LoadLibrary(实际上是 CoLoadLibrary)函数初始化服务器(Dll 被加载到客户程序进程中)并调用组件程序的 DllGetClassObject 输出函数。DllGetClassObject 函数负责创建相应的类厂对象,并返回类厂对象的 IClassFactory 接口。至此 CoGetClassObject 函数的任务完成,然后客户程序继续调用类厂对象的 CreateInstance 成员函数,由它负责 COM 对象的创建工作。
    注意:Windows COM 规范中指定你必须在服务器中完成并输出 DllGetClassObject,如果这个没有被发现,Windows 将不能传递对象到客户端,DllGetClassObject 将是进入我们的 dll(COM 服务器)的入口点。
    从上面的一番简要陈述不难看出获得 IClassFactory 接口是通过调用服务器端的 DllGetClassObject 函数获得的,传奇实际也就是从这个输出函数开始的。让我们看看它是如何实现的(如果源码中我附加了注释,请一定仔细看看,下面不再提示):

// 摘自 ComServ 单元
function DllGetClassObject(const CLSID, IID: TGUID; var Obj): HResult;
var
  Factory: TComObjectFactory;
begin
  Factory := ComClassManager.GetFactoryFromClassID(CLSID);
  if Factory <> nil
then
    if Factory.GetInterface(IID, Obj)
then
      Result := S_OK
   
else
      Result := E_NOINTERFACE
 
else
 
begin
    Pointer(Obj) := nil
;
    Result := CLASS_E_CLASSNOTAVAILABLE;
  end
;
end;


   ComClassManager 是什么?它是我们需要介绍的 Delphi COM Framework 中的第一个类。    

// 摘自 ComObj 单元
var
  ComClassManagerVar: TObject;

function
ComClassManager: TComClassManager;
begin
  if ComClassManagerVar = nil
then
    ComClassManagerVar := TComClassManager.Create;
  Result := TComClassManager(ComClassManagerVar);
end;


    每个服务器端内存在一个 TComClassManager 实例,即ComClassManagerVar 全局对象变量,它负责管理 COM 服务器中的所有类工厂(class factory)对象(本例中只有一个类工厂)。而类工厂又是什么时候创建的?其实我前面已经列出了,COM Object Wizard 生成的 SimpleCOMObject 的骨架代码的 Initialization 部分已经自动为我们创建一个 TComObjectFactory 对象:

initialization
  TComObjectFactory.Create(ComServer, TSimpleComObject, Class_SimpleComObject,
    ''''SimpleComObject'''', ''''A simple implementation of a COM Object''''
, ciMultiInstance,
    tmApartment);


    Delphi 关键字 Initialization 提示我们 dll 在被载入客户端程序进程空间时,负责创建 SimpleCOMObject 对象的类工厂 TComObjectFactory 就已经被创建了。我们知道,一个服务器端里可以包含多个 COM 对象,并且每一个独立的 COM 对象都必须相应有创建该类的类工厂,假如你设计的服务器端里有十个 COM 对象,那么肯定会有十个负责创建不同类的类工厂,这十个类工厂在程序初始化时都会被一一创建出来。这个概念一定在你的头脑中建立起来,否则后面就不好理解了。再提示一下,VCL 中定义了数种 ClassFactory 类,分别负责某一种类型的 COM 对象创建,TComObjectFactory 是其中最简单的一种[1]。那么 ComClassManager 和 TComObjectFactory 又是如何联系到一起呢?看看 TComObjectFactory 的 Constructor:

[1] [2] [3]  下一页

打印本文 打印本文 关闭窗口 关闭窗口