Delphi接口的底層實現(xiàn)教學(xué)文案_第1頁
Delphi接口的底層實現(xiàn)教學(xué)文案_第2頁
Delphi接口的底層實現(xiàn)教學(xué)文案_第3頁
Delphi接口的底層實現(xiàn)教學(xué)文案_第4頁
Delphi接口的底層實現(xiàn)教學(xué)文案_第5頁
已閱讀5頁,還剩11頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、Good is good, but better carries it.精益求精,善益求善。Delphi接口的底層實現(xiàn)-Delphi接口的底層實現(xiàn)引言接口是面向?qū)ο蟪绦蛘Z言中一個很重要的元素,它被描述為一組服務(wù)的集合,對于客戶端來說,我們關(guān)心的只是提供的服務(wù),而不必關(guān)心服務(wù)是如何實現(xiàn)的;對于服務(wù)端的類來說,如果它想實現(xiàn)某種服務(wù),實現(xiàn)與該服務(wù)相關(guān)的接口即可,它也不必與使用服務(wù)的客戶端進(jìn)行過多的交互。這種良好的設(shè)計方式已經(jīng)受到很廣泛的應(yīng)用。早在Delphi3的時候就引入了接口的概念,當(dāng)時完全是因為COM的出現(xiàn)而誕生的,但經(jīng)過這么多版本的進(jìn)化,Delphi的接口已經(jīng)成為ObjectPascal語言的

2、一部分,我們完全可以用接口來完成我們的設(shè)計,而不用考慮與COM相關(guān)的東西。那么接口在Delphi中是如何實現(xiàn)的呢,很多人想得很復(fù)雜,其實它的本質(zhì)不過也是一些簡單的數(shù)據(jù)結(jié)構(gòu)和調(diào)用規(guī)則。筆者假設(shè)讀者已經(jīng)有接口的使用經(jīng)驗,本文試圖向你展示接口在Delphi中的實現(xiàn)過程,使你在使用接口的時候,知其然而知其所以然。接口在內(nèi)存中的分布接口在概念上并不是一個實體,它需要與實現(xiàn)接口的類關(guān)聯(lián),如果脫離了這些類,接口就變得沒有意義了。但接口在內(nèi)存中仍然有其布局,它依附在對象的內(nèi)存空間中。Delphi對象本質(zhì)上是一個指向特定內(nèi)存空間的指針,這塊內(nèi)存的前四個字節(jié)是一個指針指向類的VMT表,接下來排布對象的數(shù)據(jù)成員,如

3、果對象實現(xiàn)了接口,則在后面又排著一系列指針,我們可以認(rèn)為這些指針就是對應(yīng)的接口,每個指針就指向一個接口方法表。我們來看一下簡單的例子:typeITest1=interface5347BB0D-89B7-4674-A991-5C527BE6F8A8procedureSayHello1;end;ITest2=interface567B86BB-711D-40C2-8E5E-364B742C2FF1procedureSayHello2;end;TTest=class(TInterfacedObject,ITest1,ITest2)publicprocedureSayHello1;procedureS

4、ayHello2;end;.implementationTTestprocedureTTest.SayHello1;beginshowMessage(IntToStr(FRefCount);ShowMessage(Itest1sayhello);end;procedureTTest.SayHello2;beginShowMessage(IntToStr(FRefCount);ShowMessage(Itest2sayhello);end;end.上面是兩個接口的聲明以及一個實現(xiàn)接口的類,TTest類在內(nèi)存中的分布可以用下圖來表示:其中FRefCount為父類TInterfacedObject的

5、一個成員,接下來存放的是TInterfacedObject實現(xiàn)的接口IInterface,再下來分別是TTest類實現(xiàn)的ITest2和ITest1指針。各個接口指針分別指向各自的方法表,注意ITest2和ITest1是從IInterface繼承下來的,所以自然就有了IInterface的所有方法。方法表中每個指針指向方法真正實現(xiàn)的地方,其實這個說法只是暫時的,稍后會解釋方法表中的指針真正指向的地方,并說明其原因。上面的內(nèi)存分布并非筆者隨意想出來的,而是經(jīng)過多次測試證實的,下面我們用一些代碼來證實上面分布圖:vartest:Itest2;begintest:=TTest.Create;test.

6、SayHello2;end;在證明接口的內(nèi)存布局之前,需要了解接口的變量是個什么東西,比如上面的test是什么,它的本質(zhì)上是一個指針,在沒有被賦值之前,它指向空;而得到對象的賦值之后,它指向上面分布圖中的Itest2處,對于同一個對象的多個接口變量來說,它們的“值”不一定是相等的,比如有下面的代碼:VarTest1:ITest1;Test2:ITest2;Test:TTest;BeginTest:=Ttest.Create;Test1:=Test;Test2:=Test;IfInteger(Test1)Integer(Test2)thenShowMessage(itisnoteqeual);E

7、nd;最后,會彈出一個對話框,說明Test1和Test2是不相等的;只有屬性同一種接口類型,這兩個變量才會相等,比如Test1和Test2都是Iinterface,則他們的“值”是相等的。好了,回過頭來看看之前的代碼片段吧,在第4行設(shè)置斷點,運(yùn)行程序并使上面代碼執(zhí)行,程序執(zhí)行到斷點處中止,按下Ctrl+Alt+C調(diào)用CPU窗口,可以看到下面的反匯編代碼:Unit1.pas.49:test:=TTest.Create;movdl,$01moveax,$00458e0c;eax指向VMT的地址callTObject.Create;創(chuàng)建TTest對象,eax指向TTest對象的首地址movedx,e

8、ax;edx指向eax指向的地方,edx也指向TTest對象的首地址testedx,edx;測試TTest對象是否有效jz+$03subedx,-$0c;對象首地址偏移12個字節(jié),到ITest2指針處leaeax,ebp-$04;test變量的地址是ebp-04的值,eax指向這個地址callIntfCopy;調(diào)用IntfCopy,將edx的值拷貝給eax,引用計數(shù)管理Unit1.pas.50:test.SayHello2;moveax,ebp-$04;將test指向的地址賦給eax,此時eax指向Itest2的地址movedx,eax;將eax的內(nèi)容賦給edx,此時edx指向ITest2指向

9、的方法表calldwordptredx+$0c;調(diào)用ITest2指向的方法表偏移12個字節(jié)處。.retsubedx,-$0c這一句,edx原來指向?qū)ο蟮膬?nèi)存空間,偏移12個字節(jié)剛好到哪里呢?剛好到ITest2接口指針處。接下來eax指向Test變量在棧中的地址,此時如果直接將edx賦值給eax在邏輯上也沒有錯,但這樣就不能對接口進(jìn)行引用計數(shù)的管理了。因此要調(diào)用IntfCopy,進(jìn)行接口地址的賦值,再加上一個引用計數(shù)。IntfCopy其實是調(diào)用System單元中的_IntfCopy,它的實現(xiàn)如下:procedure_IntfCopy(varDest:IInterface;constSource:

10、IInterface);$IFDEFPUREPASCALvarP:Pointer;beginP:=Pointer(Dest);/保存Dest,無引用計數(shù)ifSourcenilthenSource._AddRef;/增加Source的引用計數(shù),即增加ITest2的引用計數(shù)Pointer(Dest):=Pointer(Source);/將Source的值賦給Dest,無引用計數(shù)ifPnilthenIInterface(P)._Release;/減少目標(biāo)接口的引用計數(shù),但這里的P為空指針,所以不會調(diào)用這句end;此時的Dest參數(shù)是eax,亦即Test變量的地址,Source參數(shù)是edx,正好是對象

11、內(nèi)容空間中的ITest2的地址。我們看到其中只是對接口地址的拷貝,及增加接口的引用計數(shù)。如果Dest有內(nèi)容,則減少它的引用計數(shù),不過這里Dest為空,所以不會調(diào)用減少引用計數(shù)的代碼。接下來到calldwordptredx+$0c,edx指向ITest2指向的方法表首地址,而edx+$0c偏移到哪里呢,看看上面的內(nèi)存圖,正好到ISayHello2處。此時調(diào)用ISayHello2指向地址的代碼,我們可以簡單地認(rèn)為就是調(diào)用TTest.SayHello2。但事實上卻不是這樣的,為什么?因為在調(diào)用SayHello2之前,要先指定eax的值為TTest對象的Self指針,以此作為隱含參數(shù)傳進(jìn)SayHell

12、o2。我們可以到edx+$0c的地址看看,按F8將執(zhí)行點執(zhí)行到calldwordptredx+$0c這一句,再按F7,跳到edx+$0c的地址,可以看到下面的反匯編代碼:addeax,-$0c;eax向上偏移12個字節(jié)正好是對象內(nèi)存首地址。jmpTTest.SayHello2;跳到TTest.SayHello2處。仔細(xì)看前面的匯編碼,可以知道eax正好指向ITest2指針,向上偏移12個字節(jié)則好就到了對象內(nèi)存的首地址。接著調(diào)用TTest.SayHello2完成。通過上面的例子,不僅證明了接口在對象內(nèi)存空間中的布局,還可以得出以下結(jié)論:1.一個實現(xiàn)特定接口的對象創(chuàng)建完之后賦給該接口,編譯器作了一

13、些工作,使得接口變量指向了對象內(nèi)存中的某個特定地址。2.調(diào)用接口的方法時,實際上調(diào)用的是接口方法表中特定的地址,在該地址處編譯器計算出實現(xiàn)該接口的對象內(nèi)存首地址,再調(diào)用對象相應(yīng)的方法。接口內(nèi)存空間的形成上節(jié)說明了接口在對象內(nèi)存空間中的分布,但對象內(nèi)存空間是在運(yùn)行時生成的,那么接口的內(nèi)存空間是如何生成的呢,這一節(jié)將闡述之。在此之前,讓我們再回到上面的對象內(nèi)存圖,對象內(nèi)存的首地址是一個指針,指向一張VMT表,而Delphi的類其實也是一個指針,這個指針正好也指向VMT表。類是在編譯時就確定下來的,VMT表當(dāng)然也是編譯器生成的。VMT表在負(fù)偏移vmtIntfTable(-72)字節(jié)處是一個指針,它指

14、向下面的數(shù)據(jù)結(jié)構(gòu):PInterfaceTable=TInterfaceTable;TInterfaceTable=packedrecordEntryCount:Integer;Entries:array0.9999ofTInterfaceEntry;end;EntryCount表示對象實現(xiàn)的接口數(shù)。Entries是一個指向TInterfaceEntry結(jié)構(gòu)的數(shù)組,TInterfaceEntry表示了一個接口的進(jìn)入點,它的聲明如下:PInterfaceEntry=TInterfaceEntry;TInterfaceEntry=packedrecordIID:TGUID;VTable:Pointe

15、r;IOffset:Integer;ImplGetter:Integer;end;IID表示接口的GUID,如果接口沒有指定GUID,則它里面的值全為0。VTable指向接口的方法表。IOffset指明接口與對象首地址的偏移。ImplGetter是一個方法指針,當(dāng)IOffset不可用時指向接口的地址,一般不用,初始化為0。上面的數(shù)據(jù)結(jié)構(gòu)在編譯期就生成了,那么當(dāng)一個對象創(chuàng)建時,相應(yīng)的接口內(nèi)存是如何生成的呢。在對象創(chuàng)建完畢之后,會調(diào)用TObejct.InitInstance(Instance:Pointer)類方法初始化對象的數(shù)據(jù)??雌浯a:classfunctionTObject.InitIns

16、tance(Instance:Pointer):TObject;$IFDEFPUREPASCALvarIntfTable:PInterfaceTable;ClassPtr:TClass;I:Integer;begin/將對象全部清0FillChar(Instance,InstanceSize,0);/指定首地址為Self,即指向VMT的指針PInteger(Instance):=Integer(Self);ClassPtr:=Self;/建立對象的接口內(nèi)存分布whileClassPtrnildobegin/取得接口表IntfTable:=ClassPtr.GetInterfaceTable;i

17、fIntfTablenilthenforI:=0toIntfTable.EntryCount-1dowithIntfTable.EntriesIdobeginifVTablenilthen/對象偏移IOffset處,設(shè)定為指向VTable的指針PInteger(PChar(Instance)IOffset):=Integer(VTable);end;/繼續(xù)建立其父類的接口內(nèi)存內(nèi)存ClassPtr:=ClassPtr.ClassParent;end;Result:=Instance;end;我們看PInteger(PChar(Instance)IOffset):=Integer(VTable)這

18、一句,PChar(Instance)IOffset是對象偏移IOffset的地址,而IOffset是IntfTable.EntriesI的IOffset,這個值在編譯期就指定了,是接口到對象的偏移值。所以,經(jīng)過上面方法調(diào)用之后,對象的內(nèi)存空間就如同前面所畫一樣了?,F(xiàn)在我們對接口在內(nèi)存的來龍去脈已經(jīng)了如指掌,可以利用這些知識來實現(xiàn)一些非常的功能了。在我們的經(jīng)驗中,對象生成之后可以直接賦給一個接口,編譯器會自動將指針偏移到接口處。但如果反過來,將一個接口賦給一個對象卻是不允許的,因為信息不足啊,任何類都可以實現(xiàn)這個接口,編譯器并不知道這個接口是由那個類實現(xiàn)的,所以就無從轉(zhuǎn)換了。如果我們提供一個現(xiàn)實

19、該接口的類,再根據(jù)該類的VMT中的接口信息,就可以得到IOffset了,如此一來不就可以偏移到對象的首地址了嗎,下面的例程可以從一個接口得到實現(xiàn)該接口的對象,前提是必須提供實現(xiàn)這個接口的類:functionGetObjFromIntf(AClass:TClass;constIntf:IInterface):TObject;varPIntfTable:PInterfaceTable;IntfEntry:TInterfaceEntry;i:Integer;beginResult:=nil;/取得接口表結(jié)構(gòu)PIntfTable:=AClass.GetInterfaceTable;ifPIntfTab

20、le=nilthenExit;whileAClassnildobeginfori:=0toPIntfTable.EntryCount-1dobeginIntfEntry:=PIntfTable.Entriesi;/判斷接口表指向的地址是否和傳入接口指向的地址相同ifPPointer(Intf)=IntfEntry.VTablethenbegin/偏移到對象首地址Result:=TObject(Integer(Intf)-IntfEntry.IOffset);Exit;end;end;/繼續(xù)在父類中找AClass:=AClass.ClassParent;end;end;看下面例子:varIntf

21、:Itest2;Obj:TTest;beginIntf:=TTest.Create;Intf.SayHello2;Obj:=TTest(GetObjFromIntf(TTest,Intf);Obj.SayHello1;end;執(zhí)行上面代碼,先彈出Hello2的對話框,再彈出Hello1的對象,說明GetObjFromIntf函數(shù)執(zhí)行成功,我們實現(xiàn)了從接口到對象的轉(zhuǎn)換過程。接口的引用計數(shù)上面接口的內(nèi)存空間與COM的接口在二進(jìn)制上是兼容的,即接口就是一個指向VTable的指針,與COM兼容的還有另一個特性,就是通過引用計數(shù)自動管理COM對象的生命周期。C+程序員必須手工去管理引用計數(shù)的增減,而De

22、lphi編譯器幫我們做了這些事情,因為引用計數(shù)是有規(guī)律,只要遵循這些規(guī)律,便能自動管理引用計數(shù)的增減。IInterface的聲明如下:IInterface=interface00000000-0000-0000-C000-000000000046functionQueryInterface(constIID:TGUID;outObj):HResult;stdcall;function_AddRef:Integer;stdcall;function_Release:Integer;stdcall;end;任何實現(xiàn)IInterface的類都必須實現(xiàn)上面三個方法,其中的_AddRef和_Releas

23、e就是實現(xiàn)引用計數(shù)管理的。Delphi提供了IInterfaceObject類默認(rèn)實現(xiàn)Interface,它聲明一個成員FRefCount:Integer指定引用計數(shù),_AddRef被調(diào)用時只是將FRefCount增1:Result:=InterlockedIncrement(FRefCount);_Release被調(diào)用時,減少FRefCount,如果FRefCount為0時,即調(diào)用Destroy消毀自己:Result:=InterlockedDecrement(FRefCount);ifResult=0thenDestroy;如果即想實現(xiàn)接口,而不想通過引用計數(shù)管理生命周期的,可以在AddRef和Release中簡單地將結(jié)果返回為-1即可,TComponent類即

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論