分享

COM深入编程学习笔记2

 aaie_ 2012-10-16

COM深入编程学习笔记2 

0.接口授权

    假设读者有个经过慎重考虑后产生的类叫做TObject1,它实现Iinterface1。读者想要创建一个名为TCombinedObject的
    
的类,它实现Iinterface1船1和Iinterface2。好像是需要重新实现Iinterface1的方法,可能要从TObject1复制源代码到

TCombinedObject,对吗?

    不必这样。Delphi可让读者把一个接口的实现授权给另一个类。授权意味着:一个类包含针对另—个类的指针。
    
内部类实现一个或多个接口的功能性。外部类简单地将这些方法传递给内部类,而不是重新实现接口。
    
    下列代码实现TObject1类中的Iinterface1接口。TCombinedObject包含Tobject1引用,并且授权Iinterface1到

Fobjl的实现。

  Iinterface1 = interface
    ['{2DE825C1-EADF-11D2-B39F-0040F67455FE}']
    function Dolt1:integer;
  end;

  Iinterface2 = interface
    ['{2DE825C1-EADF-11D2-B39F-0040F67455FE}']
    function Dolt2:integer;
  end;
 
  TObject1 = class(TinterfaceObject,Iinterface1)
    protected
       function Dolt1:integer;
  end;
 
  TCombinedObject = class(TinterfaceObject,Iinterface1,Iinterface1)
    private
      FObj1:Iinterface1;
    public
      function Dolt2:integer;
      property MyIntface:Iinterface1 read FObj1 implements Iinterface1;
  end;
 
  //在使用中可以如下:
 
  procedure TForm1.btn1OnClick(Sender:TObject);
  var
    I1:Iinterface1;
    I2:Iinterface2;
  begin
    I2 := TCombinedObject.Create();
    I2.Dolt2();
    
    I1 := I2 as Iinterface1;
    I1.Dolt1();
  end;

    注意:I2 as Iinterface1语句将Iinterface2接口从TCombinedObject对象中自动分离出来即使Iinterface1实际上

是由TOjbect1实现的。这就是授权的美钞之处:用户的代码不必在意接口实际上是如何实现的。

在windows注册表(Registry),有一个键HKEY_CLASSES_ROOT\CLSID
打开该节点,发现一行行的GUID。

    每个CLSID或GUID都代表一个COM接口的实现。例如,列在CLSID第一个的是
    {00000010-0000-0010-8000-00AA006D2AE4}
    该CLSID把接口提供给miscrosoft数据访问对象(DAO),即DAO引擎。
    其GUID下面的InprocServer32键包含windows使用的信息用来在电脑中定位DA0.DLL。
   
1.COM对象和类厂(class factories)   
 
    一个COM对象位于DLL或exe。位于DLL中的COM对象被引用为进程内服务器。位于ExE中的COM对象被引用为进程外服务器。

在本章后面将讨论进程内和进程外服务器。

    COM服务器可以包含一个或COM对象。COM对象在下面小节中讨论。
 
  1)COM对象
  //Delphi封装的可以实现COM对象的类,不能从TInterfacedObject派生,而是直接从IUnknown接口派生
  TComObject = class(TObject, IUnknown, ISupportErrorInfo) //所有COM对象的基类
  private
    FController: Pointer;
    FFactory: TComObjectFactory;
    FNonCountedObject: Boolean;
    FRefCount: Integer;
    FServerExceptionHandler: IServerExceptionHandler;
    function GetController: IUnknown;
  protected
    { IUnknown }
    function IUnknown.QueryInterface = ObjQueryInterface; //映射IUnknown.QueryInterface方法
    function IUnknown._AddRef = ObjAddRef;                //映射IUnknown._AddRef方法
    function IUnknown._Release = ObjRelease;              //映射IUnknown._Release方法
    { IUnknown methods for other interfaces }
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    { ISupportErrorInfo }
    function InterfaceSupportsErrorInfo(const iid: TIID): HResult; stdcall;
  public
    constructor Create;
    constructor CreateAggregated(const Controller: IUnknown);
    constructor CreateFromFactory(Factory: TComObjectFactory;
      const Controller: IUnknown);
    destructor Destroy; override;
    procedure Initialize; virtual;
    function ObjAddRef: Integer; virtual; stdcall;
    function ObjQueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
    function ObjRelease: Integer; virtual; stdcall;
{$IFDEF MSWINDOWS}
    function SafeCallException(ExceptObject: TObject;
      ExceptAddr: Pointer): HResult; override;
{$ENDIF}
    property Controller: IUnknown read GetController;
    property Factory: TComObjectFactory read FFactory;
    property RefCount: Integer read FRefCount;
    property ServerExceptionHandler: IServerExceptionHandler
      read FServerExceptionHandler write FServerExceptionHandler;
  end;
  {$EXTERNALSYM TComObject}   
 
  2) HResult 和 OleCheck
     在COM编程中,大多数函数(除了_AddRef和_Release)都返回HResult类型的一个值。HResult是一个特殊的返回值,
  
   意味着函数调用成功还是失败,如果失败的话它也包含一个错误代码。
 
    OleCheck(MyComObject.SomeFunction);
    OleCheck(MyComObject.SomeOtherFunction);
    当调用返回HResult的COM函数时就应使用OleCheck()来检查返回值是否成功。
    {
        procedure OleCheck(Result: HResult);
        begin
           if not Succeeded(Result) then OleError(Result); //如果成功则返回S_OK =0,否则抛出异常
        end;
       
        procedure OleError(ErrorCode: HResult);
        begin
              raise EOleSysError.Create('', ErrorCode, 0);
        end;
       
        function Succeeded(Res: HResult): Boolean;
        begin
              Result := Res and $80000000 = 0;
        end;       
    }
   
   3)类工厂
   
    COM对象不是由应用程序直接例示的。相反,COM使用类工厂来创建对象。类工厂是一个对象,该对象的明确目的就是
   
 创建其它对象。每一个COM对象都有一个相关的类工厂。该类工厂负责创建在服务器中实现的COM对象。
   
    类厂把COM从实际构造一个对象的过程中分离出来。如果不是有了类厂.COM就必须直接调用对象的构造函数以便创建对象。
   
COM对于如何实现COM对象没有任何限制,并其构造是实现过程的完整部分,因此COM没有对象构造过程的直接信息是很重要的。

    尽管DLL可以提供COM可能调用的来创建一个对象实例的标准函数,但EXE并不可以。例如,DLL可能输出一个名为
   
ConstractMyComObject的函数.然后当需要创建MyComObject实例时告诉COM调用此函数。

    当创建EXE时必须注册它们的类厂,并其COM调用类厂接口以便创建COM对象。为保持一致,DLL按照和EXE相同的方式创建

并注册类厂。  类厂支持IClassFactory接口,它的定义如下:

  IClassFactory = interface(IUnknown)
    ['{00000001-0000-0000-C000-000000000046}']
    function CreateInstance(const unkOuter: IUnknown; const iid: TIID;
      out obj): HResult; stdcall;
    function LockServer(fLock: BOOL): HResult; stdcall;
  end;

    正如读者所见,IIClassFactory只定义两个函数:CreateInstance和LockServer。
   
    CreateInstance是负责创建类厂涉及的COM对象的实例的函数。一般来说读者自己不调用此函数。将看到的是,
   
COM为读者调用此函数。

    当没有在运行的客户使用服务器时,删服务器就会从内存中卸载。可以调用LockServer来迫使服务器保存在内存中。
   
调用LockServer(TRUE)来增加内部锁的计数。调用LockServer(False)来降低锁的计数。当锁的计数为零时,如果没有

客户调用服务器的话,就有可能从内存中卸裁服务器。

    注意:必须平衡LockServer(TRUE)和LockServer(FALSE)的调用才能使系统正常进行。向LockServer(TRUE)发出
   
调用而没有调用相应的LockServer(FALSE)就会使COM服务器永远驻留在内存中。

 

2.进程内的服务器(In-Process COM Server)   
 
   我们要看的第一个COM服务器是个进程内服务器。
  
   进程内服务器是由于它们在DLL内实现而获得这个名称的。因此,服务器占据了和使用它的应用程序一样的地址空间(进程)。
  
所有的进程内COM服务器输出四个标准函数: 
  DllGetClassObject,
  DllCanUnloadNow,
  DllRegisterServer,
  DllUnregisterServer;
  Borland已在Delphi中提供了这些函数的缺省实现。因此,读者不必自己编码这些函数,但是应该理解它们的用途。
 
  2.1 线程支持(Treading Support)
 
    线程支持只适合于进程内服务器,并且不适用于进程外服务器。进程内服务器可以附着在几个线程模型中的一个。
   
    进程内服务器的线程模型被存在windows注册表中。接口服务器可支持的线程模型是:
   
    1)单一的。单线程COM对象实际上根本没有线程支持。所有对COM服务器的访问都是由windows来顺序执行的,
               因此不必担心多个线程会同时访问服务器。所有COM服务器的访问都存在于线程,在此创建了COM服务器DLL。
              
    2)公寓线程(Apartment)。公寓-线程(或有时叫做单线程公寓)的COM对象可以只处理来自创建它们的线程的请求。
                            一个服务器可以输出一些COM对象,并且每个COM对象可以从一个不同的线程中创建。
                            所以,通过使用互斥体、事件、临界部分或其它同步方法访问在服务器中定义的任何
                            全局数据必须是同步的。
    3)自由的。自由线程服务器移走公寓-线程服务器施加的限制。在自由线程模型中,多个线程在任何给定COM对象上可以
               同时运行。因此,不仅必须同步访问全局数据,而且对于多个线程访问的全局数据访问必须也是同步的。
              
    4)同时公寓线程和自由线程。支持此选项的COM服务器附着在公寓线程模型和自由线程模型二者之上。这是最难支持的
                               线性模型,因为支持公寓线程和自由线程二者的COM服务器必须使他们自己对于公寓线程
                               对象实例和整个线程的排列参数数据的访问是同步的
                              
   2.2 注册服务器
     
      所有的COM服务器需要用windows注册表来正常工作。注册过程包括创建进入windows注册表所需的条目以便windows知道
     
      服务器(进程内的或进程外的)的位置和类。
     
      regSvr32  <ServerName>注册
      regSvr32 -u <ServerName>注销

    2.3 定制构造函数
     
      不要试图重载一个COM对象的构造函数。TComObject中定义的构造函数都调用了虚方法函数Initialize。
     
   如果需要为自己的COM对象提供初始化代码,只需重载Initialize方法。

    2.4 创建一个进程内COM对象的实例
   
     当用户需要在自己的客户程序代码中创建一个进程内COM对象时,一般会使用CreateComObject函数:
     function CreateComObject(const ClassID: TGUID): IUnknown;
     //封装了CoCreateInstance
     function CreateComObject(const ClassID: TGUID): IUnknown;
     begin
         OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or
        CLSCTX_LOCAL_SERVER, IUnknown, Result));
     end;
   
     //如下也是可以的使用CreateRemoteComObject,多加一个远程机器的名称,这样服务器端和客户端可以在两个机器上
     //  ifn := CreateRemoteComObject('DC2D51F3248443E',CLASS_FORMAT) as IFormattedNumber;

    一个需要注意的是:coCreateInstance内部创建负责创建COM对象类厂的实例,然后使用类厂再创建对象。创建完COM对
像后,类厂就被销毁。
    显然,如果要创建相同COM对象的众多实例,这不是非常有效的。在这种惜况下,就要自己创建一个类厂的实例并在删除
它之前使用它的coCreateInstance方法来创建COM对象.
 
  //下面代码是创建一个COM对象的类厂 ,他在加载DLL的时候就已经运行了,在卸载DLL时会自动销毁
  TComObjectFactory.Create(ComServer, TNextFit, Class_NextFit,
    'NextFit', 'Next-fit algorithm', ciMultiInstance, tmApartment);
   
     正因为有了COM对象的类厂,在客户端程序中使用CreateComObject通常返回一个IUnknown指针。要获取需要的接口指针,

应使用as操作符,如下所示:
     FOneD := CreateComObject(Class_NextFit) as IOneDBin; //获取COM对象,并授权给接口引用

3.进程外COM对象(Out-of-Process COM Server )
 
 
    进程外服务器是由于它们在EXE内实现的。进程外COM服务器不输出进程内COM服务器所需的四个函数,所以,他们在

注册表中使用不同的注册方法。要注册一个进程外的COM服务器,只需运行该服务器,把/RegServer放在命令行中。

Delphi将注册服务器和COM对象,然后就退出。要撤销注册服务器,使用命令行/unregserver
  
    如果正常运行服务器,Delphi也将注册它而没有任何命令行选项。然而,服务器应用程序将会继续运行。

   3.1 实例化(Instancing)
  
   进程外COM服务器可以支持三个实例化方法中的一个。实例化指创建多少个客户需要的实例。

   1)单实例(single Instance):指每个应用程序只允许一个COM对象的实例。每个需要COM对象实例的应用程序将产
                             生COM服务器的单独拷贝。
  
   2)多实例(Multiple Instance)是指CDMServer可以创建一个COM对象的多个拷贝。
       当客户程序请求COM对象的一个实例时,并不是启动一个新的服务器(除非服务器还未运行)。相反,由当前运行的服
       务器创建COM对象的一个实例。
   3)内部实例(Internal Only):用于不被客户应用程序使用的COM对象。能创建这种COM对象的应用程序只有包含此COM对象
                              的COM服务器。
                             
    一般来说,读者希望创建支持多实例的COM服务器。例如,假设读考已经编写了一个COM服务器,它控制对串口的访问,
 
 需要同时运行两个客户应用程序来向串口发送数据(通过COM服务器)。一个支持多实例的COM服务器可以打开串门并同时为
 
 两个客户程序服务。
 
   3.2 创建一个进程外COM对象的实例
    
     创建一个进程外服务器的COM对象实例的方法与创建进程内服务器中COM对象实例的方法是相同的。仍然可以调用
 
 CreateCOMObject函数,将需要创建的COM对象的GUDI作为一个参数传递给它。
 
  3.3 调度数据
    
     当一个程序使用一个进程外COM服务器时,在内存的一个地址上装载该程序,并且在一个不向的地址装载COM服务器。
    
 在特定的内存地址装载在调用应用程序中声明的变量,该地址表示虚内存中的地址。例如,假设客户应用程序声明一个整
 
 型变量Myht,它的内存地址为$00442830。进程外COM服务器不访问内存中的那个位置。
 
     因为一个可执行的程序不直接访问另一个可执行的程序的地址空间,Windows通过个叫做调度(marshaling)的进程在
    
调用应用程序和进程外COM服务器之间移动数据。
  
     Windows可以自动调度下列数据类型:   
     Smallint,Integer、Single、Double、Double、Currency、TDatatime、wideString、IDispatch、
     SCODE、WordBool、OleVariant、IUnknown、Shortint和Byte是自动化兼容的,意思是它们可以安全地用于自动化服
     务器(COM Automation)。

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

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多