COM深入編程學(xué)習(xí)筆記2 0.接口授權(quán)
假設(shè)讀者有個(gè)經(jīng)過慎重考慮后產(chǎn)生的類叫做TObject1,它實(shí)現(xiàn)Iinterface1。讀者想要?jiǎng)?chuàng)建一個(gè)名為TCombinedObject的 的類,它實(shí)現(xiàn)Iinterface1船1和Iinterface2。好像是需要重新實(shí)現(xiàn)Iinterface1的方法,可能要從TObject1復(fù)制源代碼到 TCombinedObject,對嗎? 不必這樣。Delphi可讓讀者把一個(gè)接口的實(shí)現(xiàn)授權(quán)給另一個(gè)類。授權(quán)意味著:一個(gè)類包含針對另—個(gè)類的指針。 內(nèi)部類實(shí)現(xiàn)一個(gè)或多個(gè)接口的功能性。外部類簡單地將這些方法傳遞給內(nèi)部類,而不是重新實(shí)現(xiàn)接口。 下列代碼實(shí)現(xiàn)TObject1類中的Iinterface1接口。TCombinedObject包含Tobject1引用,并且授權(quán)Iinterface1到 Fobjl的實(shí)現(xiàn)。 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對象中自動(dòng)分離出來即使Iinterface1實(shí)際上 是由TOjbect1實(shí)現(xiàn)的。這就是授權(quán)的美鈔之處:用戶的代碼不必在意接口實(shí)際上是如何實(shí)現(xiàn)的。 在windows注冊表(Registry),有一個(gè)鍵HKEY_CLASSES_ROOT\CLSID 打開該節(jié)點(diǎn),發(fā)現(xiàn)一行行的GUID。 每個(gè)CLSID或GUID都代表一個(gè)COM接口的實(shí)現(xiàn)。例如,列在CLSID第一個(gè)的是 {00000010-0000-0010-8000-00AA006D2AE4} 該CLSID把接口提供給miscrosoft數(shù)據(jù)訪問對象(DAO),即DAO引擎。 其GUID下面的InprocServer32鍵包含windows使用的信息用來在電腦中定位DA0.DLL。 1.COM對象和類廠(class factories) 一個(gè)COM對象位于DLL或exe。位于DLL中的COM對象被引用為進(jìn)程內(nèi)服務(wù)器。位于ExE中的COM對象被引用為進(jìn)程外服務(wù)器。 在本章后面將討論進(jìn)程內(nèi)和進(jìn)程外服務(wù)器。 COM服務(wù)器可以包含一個(gè)或COM對象。COM對象在下面小節(jié)中討論。 1)COM對象 //Delphi封裝的可以實(shí)現(xiàn)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編程中,大多數(shù)函數(shù)(除了_AddRef和_Release)都返回HResult類型的一個(gè)值。HResult是一個(gè)特殊的返回值, 意味著函數(shù)調(diào)用成功還是失敗,如果失敗的話它也包含一個(gè)錯(cuò)誤代碼。 OleCheck(MyComObject.SomeFunction); OleCheck(MyComObject.SomeOtherFunction); 當(dāng)調(diào)用返回HResult的COM函數(shù)時(shí)就應(yīng)使用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對象不是由應(yīng)用程序直接例示的。相反,COM使用類工廠來創(chuàng)建對象。類工廠是一個(gè)對象,該對象的明確目的就是 創(chuàng)建其它對象。每一個(gè)COM對象都有一個(gè)相關(guān)的類工廠。該類工廠負(fù)責(zé)創(chuàng)建在服務(wù)器中實(shí)現(xiàn)的COM對象。 類廠把COM從實(shí)際構(gòu)造一個(gè)對象的過程中分離出來。如果不是有了類廠.COM就必須直接調(diào)用對象的構(gòu)造函數(shù)以便創(chuàng)建對象。 COM對于如何實(shí)現(xiàn)COM對象沒有任何限制,并其構(gòu)造是實(shí)現(xiàn)過程的完整部分,因此COM沒有對象構(gòu)造過程的直接信息是很重要的。 盡管DLL可以提供COM可能調(diào)用的來創(chuàng)建一個(gè)對象實(shí)例的標(biāo)準(zhǔn)函數(shù),但EXE并不可以。例如,DLL可能輸出一個(gè)名為 ConstractMyComObject的函數(shù).然后當(dāng)需要?jiǎng)?chuàng)建MyComObject實(shí)例時(shí)告訴COM調(diào)用此函數(shù)。 當(dāng)創(chuàng)建EXE時(shí)必須注冊它們的類廠,并其COM調(diào)用類廠接口以便創(chuàng)建COM對象。為保持一致,DLL按照和EXE相同的方式創(chuàng)建 并注冊類廠。 類廠支持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只定義兩個(gè)函數(shù):CreateInstance和LockServer。 CreateInstance是負(fù)責(zé)創(chuàng)建類廠涉及的COM對象的實(shí)例的函數(shù)。一般來說讀者自己不調(diào)用此函數(shù)。將看到的是, COM為讀者調(diào)用此函數(shù)。 當(dāng)沒有在運(yùn)行的客戶使用服務(wù)器時(shí),刪服務(wù)器就會(huì)從內(nèi)存中卸載??梢哉{(diào)用LockServer來迫使服務(wù)器保存在內(nèi)存中。 調(diào)用LockServer(TRUE)來增加內(nèi)部鎖的計(jì)數(shù)。調(diào)用LockServer(False)來降低鎖的計(jì)數(shù)。當(dāng)鎖的計(jì)數(shù)為零時(shí),如果沒有 客戶調(diào)用服務(wù)器的話,就有可能從內(nèi)存中卸裁服務(wù)器。 注意:必須平衡LockServer(TRUE)和LockServer(FALSE)的調(diào)用才能使系統(tǒng)正常進(jìn)行。向LockServer(TRUE)發(fā)出 調(diào)用而沒有調(diào)用相應(yīng)的LockServer(FALSE)就會(huì)使COM服務(wù)器永遠(yuǎn)駐留在內(nèi)存中。 2.進(jìn)程內(nèi)的服務(wù)器(In-Process COM Server) 我們要看的第一個(gè)COM服務(wù)器是個(gè)進(jìn)程內(nèi)服務(wù)器。 進(jìn)程內(nèi)服務(wù)器是由于它們在DLL內(nèi)實(shí)現(xiàn)而獲得這個(gè)名稱的。因此,服務(wù)器占據(jù)了和使用它的應(yīng)用程序一樣的地址空間(進(jìn)程)。 所有的進(jìn)程內(nèi)COM服務(wù)器輸出四個(gè)標(biāo)準(zhǔn)函數(shù): DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; Borland已在Delphi中提供了這些函數(shù)的缺省實(shí)現(xiàn)。因此,讀者不必自己編碼這些函數(shù),但是應(yīng)該理解它們的用途。 2.1 線程支持(Treading Support) 線程支持只適合于進(jìn)程內(nèi)服務(wù)器,并且不適用于進(jìn)程外服務(wù)器。進(jìn)程內(nèi)服務(wù)器可以附著在幾個(gè)線程模型中的一個(gè)。 進(jìn)程內(nèi)服務(wù)器的線程模型被存在windows注冊表中。接口服務(wù)器可支持的線程模型是: 1)單一的。單線程COM對象實(shí)際上根本沒有線程支持。所有對COM服務(wù)器的訪問都是由windows來順序執(zhí)行的, 因此不必?fù)?dān)心多個(gè)線程會(huì)同時(shí)訪問服務(wù)器。所有COM服務(wù)器的訪問都存在于線程,在此創(chuàng)建了COM服務(wù)器DLL。 2)公寓線程(Apartment)。公寓-線程(或有時(shí)叫做單線程公寓)的COM對象可以只處理來自創(chuàng)建它們的線程的請求。 一個(gè)服務(wù)器可以輸出一些COM對象,并且每個(gè)COM對象可以從一個(gè)不同的線程中創(chuàng)建。 所以,通過使用互斥體、事件、臨界部分或其它同步方法訪問在服務(wù)器中定義的任何 全局?jǐn)?shù)據(jù)必須是同步的。 3)自由的。自由線程服務(wù)器移走公寓-線程服務(wù)器施加的限制。在自由線程模型中,多個(gè)線程在任何給定COM對象上可以 同時(shí)運(yùn)行。因此,不僅必須同步訪問全局?jǐn)?shù)據(jù),而且對于多個(gè)線程訪問的全局?jǐn)?shù)據(jù)訪問必須也是同步的。 4)同時(shí)公寓線程和自由線程。支持此選項(xiàng)的COM服務(wù)器附著在公寓線程模型和自由線程模型二者之上。這是最難支持的 線性模型,因?yàn)橹С止⒕€程和自由線程二者的COM服務(wù)器必須使他們自己對于公寓線程 對象實(shí)例和整個(gè)線程的排列參數(shù)數(shù)據(jù)的訪問是同步的 2.2 注冊服務(wù)器 所有的COM服務(wù)器需要用windows注冊表來正常工作。注冊過程包括創(chuàng)建進(jìn)入windows注冊表所需的條目以便windows知道 服務(wù)器(進(jìn)程內(nèi)的或進(jìn)程外的)的位置和類。 regSvr32 <ServerName>注冊 regSvr32 -u <ServerName>注銷 2.3 定制構(gòu)造函數(shù) 不要試圖重載一個(gè)COM對象的構(gòu)造函數(shù)。TComObject中定義的構(gòu)造函數(shù)都調(diào)用了虛方法函數(shù)Initialize。 如果需要為自己的COM對象提供初始化代碼,只需重載Initialize方法。 2.4 創(chuàng)建一個(gè)進(jìn)程內(nèi)COM對象的實(shí)例 當(dāng)用戶需要在自己的客戶程序代碼中創(chuàng)建一個(gè)進(jìn)程內(nèi)COM對象時(shí),一般會(huì)使用CreateComObject函數(shù): 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,多加一個(gè)遠(yuǎn)程機(jī)器的名稱,這樣服務(wù)器端和客戶端可以在兩個(gè)機(jī)器上 // ifn := CreateRemoteComObject('DC2D51F3248443E',CLASS_FORMAT) as IFormattedNumber; 一個(gè)需要注意的是:coCreateInstance內(nèi)部創(chuàng)建負(fù)責(zé)創(chuàng)建COM對象類廠的實(shí)例,然后使用類廠再創(chuàng)建對象。創(chuàng)建完COM對 像后,類廠就被銷毀。 顯然,如果要?jiǎng)?chuàng)建相同COM對象的眾多實(shí)例,這不是非常有效的。在這種惜況下,就要自己創(chuàng)建一個(gè)類廠的實(shí)例并在刪除 它之前使用它的coCreateInstance方法來創(chuàng)建COM對象. //下面代碼是創(chuàng)建一個(gè)COM對象的類廠 ,他在加載DLL的時(shí)候就已經(jīng)運(yùn)行了,在卸載DLL時(shí)會(huì)自動(dòng)銷毀 TComObjectFactory.Create(ComServer, TNextFit, Class_NextFit, 'NextFit', 'Next-fit algorithm', ciMultiInstance, tmApartment); 正因?yàn)橛辛薈OM對象的類廠,在客戶端程序中使用CreateComObject通常返回一個(gè)IUnknown指針。要獲取需要的接口指針, 應(yīng)使用as操作符,如下所示: FOneD := CreateComObject(Class_NextFit) as IOneDBin; //獲取COM對象,并授權(quán)給接口引用 3.進(jìn)程外COM對象(Out-of-Process COM Server ) 進(jìn)程外服務(wù)器是由于它們在EXE內(nèi)實(shí)現(xiàn)的。進(jìn)程外COM服務(wù)器不輸出進(jìn)程內(nèi)COM服務(wù)器所需的四個(gè)函數(shù),所以,他們在 注冊表中使用不同的注冊方法。要注冊一個(gè)進(jìn)程外的COM服務(wù)器,只需運(yùn)行該服務(wù)器,把/RegServer放在命令行中。 Delphi將注冊服務(wù)器和COM對象,然后就退出。要撤銷注冊服務(wù)器,使用命令行/unregserver 如果正常運(yùn)行服務(wù)器,Delphi也將注冊它而沒有任何命令行選項(xiàng)。然而,服務(wù)器應(yīng)用程序?qū)?huì)繼續(xù)運(yùn)行。 3.1 實(shí)例化(Instancing) 進(jìn)程外COM服務(wù)器可以支持三個(gè)實(shí)例化方法中的一個(gè)。實(shí)例化指創(chuàng)建多少個(gè)客戶需要的實(shí)例。 1)單實(shí)例(single Instance):指每個(gè)應(yīng)用程序只允許一個(gè)COM對象的實(shí)例。每個(gè)需要COM對象實(shí)例的應(yīng)用程序?qū)a(chǎn) 生COM服務(wù)器的單獨(dú)拷貝。 2)多實(shí)例(Multiple Instance)是指CDMServer可以創(chuàng)建一個(gè)COM對象的多個(gè)拷貝。 當(dāng)客戶程序請求COM對象的一個(gè)實(shí)例時(shí),并不是啟動(dòng)一個(gè)新的服務(wù)器(除非服務(wù)器還未運(yùn)行)。相反,由當(dāng)前運(yùn)行的服 務(wù)器創(chuàng)建COM對象的一個(gè)實(shí)例。 3)內(nèi)部實(shí)例(Internal Only):用于不被客戶應(yīng)用程序使用的COM對象。能創(chuàng)建這種COM對象的應(yīng)用程序只有包含此COM對象 的COM服務(wù)器。 一般來說,讀者希望創(chuàng)建支持多實(shí)例的COM服務(wù)器。例如,假設(shè)讀考已經(jīng)編寫了一個(gè)COM服務(wù)器,它控制對串口的訪問, 需要同時(shí)運(yùn)行兩個(gè)客戶應(yīng)用程序來向串口發(fā)送數(shù)據(jù)(通過COM服務(wù)器)。一個(gè)支持多實(shí)例的COM服務(wù)器可以打開串門并同時(shí)為 兩個(gè)客戶程序服務(wù)。 3.2 創(chuàng)建一個(gè)進(jìn)程外COM對象的實(shí)例 創(chuàng)建一個(gè)進(jìn)程外服務(wù)器的COM對象實(shí)例的方法與創(chuàng)建進(jìn)程內(nèi)服務(wù)器中COM對象實(shí)例的方法是相同的。仍然可以調(diào)用 CreateCOMObject函數(shù),將需要?jiǎng)?chuàng)建的COM對象的GUDI作為一個(gè)參數(shù)傳遞給它。 3.3 調(diào)度數(shù)據(jù) 當(dāng)一個(gè)程序使用一個(gè)進(jìn)程外COM服務(wù)器時(shí),在內(nèi)存的一個(gè)地址上裝載該程序,并且在一個(gè)不向的地址裝載COM服務(wù)器。 在特定的內(nèi)存地址裝載在調(diào)用應(yīng)用程序中聲明的變量,該地址表示虛內(nèi)存中的地址。例如,假設(shè)客戶應(yīng)用程序聲明一個(gè)整 型變量Myht,它的內(nèi)存地址為$00442830。進(jìn)程外COM服務(wù)器不訪問內(nèi)存中的那個(gè)位置。 因?yàn)橐粋€(gè)可執(zhí)行的程序不直接訪問另一個(gè)可執(zhí)行的程序的地址空間,Windows通過個(gè)叫做調(diào)度(marshaling)的進(jìn)程在 調(diào)用應(yīng)用程序和進(jìn)程外COM服務(wù)器之間移動(dòng)數(shù)據(jù)。 Windows可以自動(dòng)調(diào)度下列數(shù)據(jù)類型: Smallint,Integer、Single、Double、Double、Currency、TDatatime、wideString、IDispatch、 SCODE、WordBool、OleVariant、IUnknown、Shortint和Byte是自動(dòng)化兼容的,意思是它們可以安全地用于自動(dòng)化服 務(wù)器(COM Automation)。 |
|