inherited在消息中的作用(编译器根据inherited所在的函数,直接转换成对祖先类同名动态函数的调用,或者转换成对DefaultHandler的调用)
2014-05-01 19:49
316 查看
好奇一下。看来Object Pascal确实与Windows深入结合了。
在message处理中和其他不一样的是inherited不会因为没有在祖先中找到一样的函数或者方法而将inherited失效,他会传入缺省的消息处理.
这里调用TFORM1的祖先的消息处理,由于tform和tcustomform没有这个实现UM_Test函数,所以直接调用的是tcustomform的defaulthandle.(注意这个方法是对twincontrol的override)。
但是,如果本类重载了DefaultHandler函数,就会直接调用本类的DefaultHandler函数:
顺便再看看这样改写的效果:
理论解释:
因为WndProc里面调用了Dispatch,而Dispatch实际上是尝试调用动态方法,在动态方法表中查找Index是指定的方法,如果一直找到TObject都找不到Index是这个的方法,就会调用DefaultHandler。
这里面有几个概念,动态方法表和虚方法表,不要混了。
动态方法实际上在Class中是个最大65536的函数数组。调用的时候会根据Index到这个数组中来调用。而消息方法实际就是动态方法,方法声明后面的Message实际上就是Index。
VCL之所以对消息处理效率比VC等高,就是因为这个动态方法表,实际调用的就是用消息做下标直接调用方法。最多就是找几层继承的父类。而比VC的遍历要快。
Inherited动态方法的时候就是到父类的动态方法表找Index等于本方法Index的方法。如果父类没有这个方法就再到父类的父类找。都找不到就调用DefaultHandler。
动态方法和虚方法相比优点是省内存,另外能对Message的处理比较方便。虚方法不能做到对消息的方便处理。
虚方法则比较费内存,每个派生类都要有一套虚方法表。但是被调用的时候比动态方法要快,不需要到父类去找。
理论解释2:
你恰恰理解反了,不是Inherited会调用Dispatch,而是Dispatch会调用动态方法.
消息的调用次序实际上是
WndProc->Dispatch->动态方法或者DefaultHandler方法.
TControl.WndProc的最后一句就是Dispatch.
然后Dispatch就找Index的动态方法调用.
如果找不到就调用到DefaultHandler.
如果找到,就调用动态方法.
如果动态方法里面调用了Inherited,那么实际上inherited是编译器处理的.如果在祖宗里面有对应index的方法,那么就直接编译成调用该方法.如果祖宗里面没有该index的方法编译成调用DefaultHandler(存疑).
实际上这样处理效率比运行时处理要快.
Inherited必须由编译器处理的,因为Inherited有两种含义.动态方法和虚方法的实现机制不同必然要根据方法编译成不同的代码.
====================================================================
对应的pascal代码如下
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; const UM_Test = WM_USER + 100; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure MyMessage(var Msg: TMessage); message UM_Test; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin Perform(UM_Test, 0, 0); end; procedure TForm1.MyMessage(var Msg: TMessage); begin inherited; ShowMessage('Hello'); end; end.
在message处理中和其他不一样的是inherited不会因为没有在祖先中找到一样的函数或者方法而将inherited失效,他会传入缺省的消息处理.
这里调用TFORM1的祖先的消息处理,由于tform和tcustomform没有这个实现UM_Test函数,所以直接调用的是tcustomform的defaulthandle.(注意这个方法是对twincontrol的override)。
但是,如果本类重载了DefaultHandler函数,就会直接调用本类的DefaultHandler函数:
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; const UM_Test = WM_USER + 100; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure MyMessage(var Msg: TMessage); message UM_Test; procedure DefaultHandler(var Message); override; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin Perform(UM_Test, 0, 0); end; procedure TForm1.DefaultHandler(var Message); begin with TMessage(Message) do begin if Msg = UM_Test then ShowMessage('DefaultHandler'); end; inherited; end; procedure TForm1.MyMessage(var Msg: TMessage); begin inherited; ShowMessage('Hello'); end; end.
顺便再看看这样改写的效果:
procedure TForm1.DefaultHandler(var Message); begin with TMessage(Message) do begin if Msg = UM_Test then ShowMessage('DefaultHandler'); if Msg = WM_SETTEXT then ShowMessage('WM_SETTEXT'); end; inherited; end;
理论解释:
因为WndProc里面调用了Dispatch,而Dispatch实际上是尝试调用动态方法,在动态方法表中查找Index是指定的方法,如果一直找到TObject都找不到Index是这个的方法,就会调用DefaultHandler。
这里面有几个概念,动态方法表和虚方法表,不要混了。
动态方法实际上在Class中是个最大65536的函数数组。调用的时候会根据Index到这个数组中来调用。而消息方法实际就是动态方法,方法声明后面的Message实际上就是Index。
VCL之所以对消息处理效率比VC等高,就是因为这个动态方法表,实际调用的就是用消息做下标直接调用方法。最多就是找几层继承的父类。而比VC的遍历要快。
Inherited动态方法的时候就是到父类的动态方法表找Index等于本方法Index的方法。如果父类没有这个方法就再到父类的父类找。都找不到就调用DefaultHandler。
动态方法和虚方法相比优点是省内存,另外能对Message的处理比较方便。虚方法不能做到对消息的方便处理。
虚方法则比较费内存,每个派生类都要有一套虚方法表。但是被调用的时候比动态方法要快,不需要到父类去找。
理论解释2:
你恰恰理解反了,不是Inherited会调用Dispatch,而是Dispatch会调用动态方法.
消息的调用次序实际上是
WndProc->Dispatch->动态方法或者DefaultHandler方法.
TControl.WndProc的最后一句就是Dispatch.
然后Dispatch就找Index的动态方法调用.
如果找不到就调用到DefaultHandler.
如果找到,就调用动态方法.
如果动态方法里面调用了Inherited,那么实际上inherited是编译器处理的.如果在祖宗里面有对应index的方法,那么就直接编译成调用该方法.如果祖宗里面没有该index的方法编译成调用DefaultHandler(存疑).
实际上这样处理效率比运行时处理要快.
Inherited必须由编译器处理的,因为Inherited有两种含义.动态方法和虚方法的实现机制不同必然要根据方法编译成不同的代码.
====================================================================
procedure TObject.Dispatch(var Message); asm PUSH ESI MOV SI,[EDX] //获取index OR SI,SI JE @@default //如果是0,到default,也就是调用DefaultHandler CMP SI,0C000H JAE @@default //如果不在合理范围内,到default,也就是调用DefaultHandler PUSH EAX MOV EAX,[EAX] CALL GetDynaMethod //获取动态方法 POP EAX JE @@default //如果GetDynaMethod返回的是nil,到default,也就是调用DefaultHandler MOV ECX,ESI POP ESI JMP ECX //跳转到动态方法 @@default: POP ESI MOV ECX,[EAX] JMP DWORD PTR [ECX] + VMTOFFSET TObject.DefaultHandler end; procedure GetDynaMethod; { function GetDynaMethod(vmt: TClass; selector: Smallint) : Pointer; } asm { -> EAX vmt of class } { SI dynamic method index } { <- ESI pointer to routine } { ZF = 0 if found } { trashes: EAX, ECX } PUSH EDI XCHG EAX,ESI JMP @@haveVMT @@outerLoop: MOV ESI,[ESI] @@haveVMT: MOV EDI,[ESI].vmtDynamicTable //根据vmt获取该类的动态方法表 TEST EDI,EDI JE @@parent MOVZX ECX,word ptr [EDI] PUSH ECX ADD EDI,2 REPNE SCASW JE @@found //跳到found POP ECX @@parent: MOV ESI,[ESI].vmtParent //为空,那么找父类的 TEST ESI,ESI //测试父类是否为空 JNE @@outerLoop //跳出循环 JMP @@exit //退出 @@found: POP EAX ADD EAX,EAX SUB EAX,ECX { this will always clear the Z-flag ! } MOV ESI,[EDI+EAX*2-4] @@exit: POP EDI end;
对应的pascal代码如下
procedure TObject.Dispatch(var Message); type //THandlerProc = procedure(Self: Pointer; var Message) { of object }; THandlerProc = procedure(var Message) of object; var MsgID: Word; Addr: Pointer; M: THandlerProc; begin MsgID := TDispatchMessage(Message).MsgID;//消息id,也就是动态方法表的索引 if (MsgID <> 0) and (MsgID < $C000) then begin Addr := GetDynaMethod(PPointer(Self)^, MsgID);//获取class的动态方法表 if Addr <> nil then//如果拿到了动态方法就调用 begin //THandlerProc(Addr)(Self, Message) TMethod(M).Data := Self; TMethod(M).Code := Addr; M(Message); end else Self.DefaultHandler(Message);//如果找不到动态方法则调用defaultHandler end else Self.DefaultHandler(Message);//如果index范围不在应该在的范围内也调用DefaultHandler end; function GetDynaMethod(vmt: TClass; selector: SmallInt): Pointer; type TDynaMethodTable = record Count: Word; Selectors: array[0..9999999] of SmallInt; {Addrs: array[0..0] of Pointer;} end; PDynaMethodTable = ^TDynaMethodTable; var dynaTab: PDynaMethodTable; Parent: Pointer; Addrs: PPointer; I: Cardinal; begin while True do begin dynaTab := PPointer(PByte(vmt) + vmtDynamicTable)^;//根据vmt获取该类的动态方法表 if dynaTab <> nil then begin for I := 0 to dynaTab.Count - 1 do if dynaTab.Selectors[I] = selector then begin Addrs := PPointer(PByte(@dynaTab.Selectors) + dynaTab.Count * SizeOf(dynaTab.Selectors[0])); Result := PPointer(PByte(Addrs) + I * SizeOf(Pointer))^; Exit;//能找到则退出 end; end; Parent := PPointer(PByte(vmt) + vmtParent)^;//找不到则找父类的 if Parent = nil then Break;//如果父类是nil,也就是tobject了,则退出 vmt := PPointer(Parent)^; end; Result := nil; end;
相关文章推荐
- C++ 消息(成员函数调用)的动态绑定
- VS中c++文件调用c 函数 ,fatal error C1853 预编译头文件来自编译器的早期版本号,或者预编译头为 C++ 而在 C 中使用它(或相反)
- NSInvocaion用来替代performSelector传多个参数时,以及根据函数签名来动态调用时
- C# 调用WebService的3种方式 :直接调用、根据wsdl生成webservice的.cs文件及生成dll调用、动态调用
- C#实现根据字符串调用同名的函数
- 根据字符串名称动态调用Python的函数和对象方法
- 待补遗(4)[C]如何让编译器调用自定义函数而非同名库函数
- TWinControl.DefaultHandler里的CallWindowProc(FDefWndProc)还挺有深意的,TButton对WM_PAINT消息的处理就是靠它来处理的(以前不明白为什么总是要调用inherited,其实就是没有明白TWinControl.DefaultHandler的真正用处,而且还很有用)
- 根据函数名动态调用
- 递归算法是一种直接或者间接调用自身函数或者方法的算法
- linux-C直接调用SO动态库和生成SO动态库的函数
- orcle 根据时间 动态删除原表数据,调用动态创建表函数。插入新表数据
- 宏和函数在同名在一个头或者一个文件的作用
- VS中c++文件调用c 函数 ,fatal error C1853 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)
- 面试题:为什么其他语言里叫函数调用,objective-c里则是给对象发消息(或者谈下对runtime的理解)
- Javascript中调用一个函数(对象),使用new或者直接调用有什么区别?
- 没做过编译器就是被人欺——从一道变态的类型转换题猜编译器的行为(指针直接调用自己的非虚函数,当时是什么类型就是什么,但数据成员仍用自己的)
- 异常信息:CLR无法从COM 上下文0x645e18 转换为COM上下文0x645f88,这种状态已持续60秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的情况下处理一个运行时间非常长的操作.这种情况通常会影响到
- 函数调用时的实参为变量时 -- C语言的每条可执行语句最终都将转换成二进制的机器指令
- Fragment如何调用所在Activity的dispatchTouchEvent(MotionEvent ev)函数