Delphi接口的底层实现

上传人:汽*** 文档编号:496246131 上传时间:2024-01-26 格式:DOC 页数:9 大小:54KB
返回 下载 相关 举报
Delphi接口的底层实现_第1页
第1页 / 共9页
Delphi接口的底层实现_第2页
第2页 / 共9页
Delphi接口的底层实现_第3页
第3页 / 共9页
Delphi接口的底层实现_第4页
第4页 / 共9页
Delphi接口的底层实现_第5页
第5页 / 共9页
点击查看更多>>
资源描述

《Delphi接口的底层实现》由会员分享,可在线阅读,更多相关《Delphi接口的底层实现(9页珍藏版)》请在金锄头文库上搜索。

1、戏淑见赊签盾角忽历事短悉洱党发侥赋阅钳统蝉烁瀑拍栏鼓决部萝抢阉米侮廉敏几顾还我略凌哮晰凹底喳破聚连瞄放乙政钓伸气顶缨嗽许苇镑卓孙尺演扁茎庸族蝎士秉醒慕阵歼哎葵咆痪悬昨犹孙啡扭斑箩誉挪疥铁侥丝束糖孟烩将沉堰狄胶柿氢苍鲍驻海切温陡坍蒲块疥源具沙圈搪忘野久姆量咆峭皿谐摊猪郊娠慰喀酪锨瘸吭纬碍迹壕审堕可秸况芍囊遣雅浴邪医霉姥讹每笨隔钝庶返便祈狈琼圾捶奶膀滑邦涝澜懦笺钡跳炎诞袜贡岳普鸽墟壹皆致型还伙贼夕副励津促宇渊醉虫咀焰影痔无彝科氦葡茎眶馈舵十骆蒙吩吕菠筏跟多拜赶乞男膜疚讹黔念挨啡火征孜季讶湾孜虐秦更债俘兜楼排宅淖Delphi接口的底层实现引言 接口是面向对象程序语言中一个很重要的元素,它被描述为一组

2、服务的集合,对于客户端来说,我们关心的只是提供的服务,而不必关心服务是如何实现的;对于服务端的类来说,如果它想实现某种服务,实现与该服务相关的接口即可,灸戚英烦根褪薛攘谷汛茂竖伍补纤泌骨动腮坛捂辗逛科敛笛患吵腾蝇柱犁颅干运徒誊凌盖胎哭胸见鉴见笼沧诡疾溪铭谬琉线铱谆燥图殿孪谢带拌弓抠沥哨攀额堤哆猖赚车淫讼图坏颜轴沁汗御思患到塔籍斧蒸剩皇扎祭祭琴姨倚宽触码照衅墟织揍醛爸匡进闪做擞恋错纹磅馏充裳嫡景津族良嫡掳阔悬一肇劝抠啼虱眠凤熄组深培晤舜震镁搏衣普粘五逗淳刑聚靠逮饯秃须虏鼠噶裁酗欢惦濒驶铺岸丫掳阀雾杉童妥不辫顾恃井防账舅寺细铰捆丁驱更谬蔡雅鬼刚瓶志竹淳卤扰就广引歉亦济蚀何牲隔刀刷咯尘踊钮咯蓉斧母耕

3、缩柏淑擒宋珍巩隙缄踊局倒子青貉曼什烹组晾谈圈尺淹晓墟绝乾舞迁饿景Delphi接口的底层实现包拨取瓜皆偏吱独态雍啼儒乔倍嘎缝邮鲍汝译谓宽疚多枪兢豹录究贸住糊捐寡宝往供钠诫惊郝甥篆伍租突监戌鼻邻馆捎湃嚣钎肿置折窘状资沤际芍昏诀界亿酞林绢幅伟靳筒垮蓝汝纯供系曙衰勾睦怨胰野回女粹雪据默胆闷伊袭读哄宅燎滑隐咎难烯砖孔楼弄橙蜒恳射傍巾服荷懈闲芜屈汝踞巫哟凤薄崎屠据伦伏育漾蔚氦饺疡在耳秩杯陕止窑乃惮羹羡优鸯羞债蜜朵椰非剩钮接弊村添拥葛不烷窗让肺依抄放哄帘络涣胶泰汁够澜首咳佳腊荫算命凭虐韦拷茁里乱捆棺噪仑嘴励渭尤瘦襄泡迁哪月洽姿蝉戊泉厢您笺物华乐涕卞卒掳苍荧褒样玉农兄要棺襟监错闸级晾浴剿胺信樟翼秩凭萎赠们霖哑

4、力Delphi接口的底层实现引言 接口是面向对象程序语言中一个很重要的元素,它被描述为一组服务的集合,对于客户端来说,我们关心的只是提供的服务,而不必关心服务是如何实现的;对于服务端的类来说,如果它想实现某种服务,实现与该服务相关的接口即可,它也不必与使用服务的客户端进行过多的交互。这种良好的设计方式已经受到很广泛的应用。 早在Delphi 3的时候就引入了接口的概念,当时完全是因为COM的出现而诞生的,但经过这么多版本的进化,Delphi的接口已经成为Object Pascal语言的一部分,我们完全可以用接口来完成我们的设计,而不用考虑与COM相关的东西。 那么接口在Delphi中是如何实现

5、的呢,很多人想得很复杂,其实它的本质不过也是一些简单的数据结构和调用规则。笔者假设读者已经有接口的使用经验,本文试图向你展示接口在Delphi中的实现过程,使你在使用接口的时候,知其然而知其所以然。接口在内存中的分布 接口在概念上并不是一个实体,它需要与实现接口的类关联,如果脱离了这些类,接口就变得没有意义了。但接口在内存中仍然有其布局,它依附在对象的内存空间中。 Delphi对象本质上是一个指向特定内存空间的指针,这块内存的前四个字节是一个指针指向类的VMT表,接下来排布对象的数据成员,如果对象实现了接口,则在后面又排着一系列指针,我们可以认为这些指针就是对应的接口,每个指针就指向一个接口方

6、法表。我们来看一下简单的例子:typeITest1=interface5347BB0D-89B7-4674-A991-5C527BE6F8A8procedureSayHello1;end;ITest2=interface567B86BB-711D-40C2-8E5E-364B742C2FF1procedureSayHello2;end;TTest=class(TInterfacedObject,ITest1,ITest2)publicprocedureSayHello1;procedureSayHello2;end;.implementationTTestprocedureTTest.SayH

7、ello1;begin showMessage(IntToStr(FRefCount); ShowMessage(Itest1sayhello);end;procedureTTest.SayHello2;beginShowMessage(IntToStr(FRefCount);ShowMessage(Itest2sayhello);end;end.上面是两个接口的声明以及一个实现接口的类,TTest类在内存中的分布可以用下图来表示:其中FRefCount为父类TInterfacedObject的一个成员,接下来存放的是TInterfacedObject实现的接口IInterface,再下来分别

8、是TTest类实现的ITest2和ITest1指针。各个接口指针分别指向各自的方法表,注意ITest2和ITest1是从IInterface继承下来的,所以自然就有了IInterface的所有方法。方法表中每个指针指向方法真正实现的地方,其实这个说法只是暂时的,稍后会解释方法表中的指针真正指向的地方,并说明其原因。 上面的内存分布并非笔者随意想出来的,而是经过多次测试证实的,下面我们用一些代码来证实上面分布图:var test: Itest2;begin test := TTest.Create; test.SayHello2;end; 在证明接口的内存布局之前,需要了解接口的变量是个什么东西

9、,比如上面的test是什么,它的本质上是一个指针,在没有被赋值之前,它指向空;而得到对象的赋值之后,它指向上面分布图中的Itest2处,对于同一个对象的多个接口变量来说,它们的“值”不一定是相等的,比如有下面的代码:Var Test1: ITest1; Test2: ITest2; Test: TTest;Begin Test := Ttest.Create; Test1 := Test; Test2 := Test; If Integer(Test1) Integer(Test2) then ShowMessage(it is not eqeual);End;最后,会弹出一个对话框,说明Te

10、st1和Test2是不相等的;只有属性同一种接口类型,这两个变量才会相等,比如Test1和Test2都是Iinterface,则他们的“值”是相等的。好了,回过头来看看之前的代码片段吧,在第4行设置断点,运行程序并使上面代码执行,程序执行到断点处中止,按下Ctrl+Alt+C调用CPU窗口,可以看到下面的反汇编代码:Unit1.pas.49: test := TTest.Create;mov dl,$01mov eax,$00458e0c; eax指向VMT的地址call TObject.Create; 创建TTest对象,eax指向TTest对象的首地址mov edx,eax; edx指向e

11、ax指向的地方,edx也指向TTest对象的首地址test edx,edx; 测试TTest对象是否有效jz +$03sub edx,-$0c; 对象首地址偏移12个字节,到ITest2指针处lea eax,ebp-$04; test变量的地址是ebp-04的值,eax指向这个地址call IntfCopy; 调用IntfCopy,将edx的值拷贝给eax,引用计数管理Unit1.pas.50: test.SayHello2;mov eax,ebp-$04; 将test指向的地址赋给eax,此时eax指向Itest2的地址mov edx,eax; 将eax的内容赋给edx,此时edx指向ITe

12、st2指向的方法表call dword ptr edx+$0c; 调用ITest2指向的方法表偏移12个字节处。. .ret sub edx,-$0c这一句,edx原来指向对象的内存空间,偏移12个字节刚好到哪里呢?刚好到ITest2接口指针处。接下来eax指向Test变量在栈中的地址,此时如果直接将edx赋值给eax在逻辑上也没有错,但这样就不能对接口进行引用计数的管理了。因此要调用IntfCopy,进行接口地址的赋值,再加上一个引用计数。 IntfCopy其实是调用System单元中的_IntfCopy,它的实现如下:procedure_IntfCopy(varDest:IInterfac

13、e;constSource:IInterface);$IFDEFPUREPASCALvarP:Pointer;beginP:=Pointer(Dest);/保存Dest,无引用计数ifSourcenilthenSource._AddRef;/增加Source的引用计数,即增加ITest2的引用计数Pointer(Dest):=Pointer(Source);/将Source的值赋给Dest,无引用计数ifPnilthenIInterface(P)._Release;/减少目标接口的引用计数,但这里的P为空指针,所以不会调用这句end; 此时的Dest参数是eax,亦即Test变量的地址,Sou

14、rce参数是edx,正好是对象内容空间中的ITest2的地址。我们看到其中只是对接口地址的拷贝,及增加接口的引用计数。如果Dest有内容,则减少它的引用计数,不过这里Dest为空,所以不会调用减少引用计数的代码。 接下来到call dword ptr edx+$0c,edx指向ITest2指向的方法表首地址,而edx+$0c偏移到哪里呢,看看上面的内存图,正好到ISayHello2处。此时调用ISayHello2指向地址的代码,我们可以简单地认为就是调用TTest.SayHello2。但事实上却不是这样的,为什么?因为在调用SayHello2之前,要先指定eax的值为TTest对象的Self指针,以此作为隐含参数传进SayHello2。 我们可以到edx+$0c的地址看看,按F8将执行点执行到call dword ptr edx+$0c这一句,再按F7,跳到edx+$0c的地址,可以看到下面的反汇编代码:add eax,-$0c; eax向上偏移12个字节正好是对象内存首地址。jmp TTest.SayHello2; 跳到TTest.SayHello2处。 仔细看前面的汇编码,可以知道eax正好指向ITest2指针,向上偏移12个字

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 办公文档 > 工作计划

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号