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)。 |
|