好奇一下。看来Object Pascal确实与Windows深入结合了。

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls; const
UM_Test = WM_USER + ; 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, , );
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 + ; 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, , );
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,
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*-] @@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 <> ) 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[..] 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 := to dynaTab.Count - do
if dynaTab.Selectors[I] = selector then
begin
Addrs := PPointer(PByte(@dynaTab.Selectors) + dynaTab.Count * SizeOf(dynaTab.Selectors[]));
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;

inherited在消息中的作用(编译器根据inherited所在的函数,直接转换成对祖先类同名动态函数的调用,或者转换成对DefaultHandler的调用)的更多相关文章

  1. python装饰器中@wraps作用--修复被装饰后的函数名等属性的改变

    Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的de ...

  2. SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用

    近段时间以来,一直在探究SQL Server查询性能的问题,当然也漫无目的的查找了很多资料,也从网上的大神们的文章中学到了很多,在这里,向各位大神致敬.正是受大神们无私奉献精神的影响,所以小弟也作为回 ...

  3. SLAM+语音机器人DIY系列:(二)ROS入门——8.理解roslaunch在大型项目中的作用

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...

  4. __setup 在内核中的作用【转】

    本文转载自:http://blog.csdn.net/lanmanck/article/details/7613305 本文来自: http://blog.chinaunix.net/uid-1379 ...

  5. [转载]java中import作用详解

    [转载]java中import作用详解 来源: https://blog.csdn.net/qq_25665807/article/details/74747868 这篇博客讲的真的很清楚,这个作者很 ...

  6. static在c\c++中的作用(翁恺c++公开课[28-29]学习笔记)

    static相对来说是一个较复杂的修饰符,c++中的static在c的基础之上又包含了static在类中的应用(也就是说多了static的成员变量和static的成员函数):c\c++中静态变量.对象 ...

  7. static在C/C++中的作用-(转自华山大师兄)

    1.先来介绍它的第一条也是最重要的一条:隐藏.(static函数,static变量均可) 当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性.举例来说明.同时编译两个源文件 ...

  8. ZeroMQ接口函数之 :zmq_msg_copy - 把一个消息的内容复制到另一个消息中

    ZeroMQ 官方地址 :http://api.zeromq.org/4-1:zmq_msg_copy zmq_msg_copy(3)   ØMQ Manual - ØMQ/3.2.5 Name zm ...

  9. 微软承诺将在今年的 Visual C++ 更新中加入 Clang 编译器

    微软最近发布将在2015年11月 Visual C++ 更新中加入 Clang 编译器 ,Clang 开源编译器以相比GCC更快的编译速度和更优的错误提示著称. Clang关于C,C++,及Objec ...

随机推荐

  1. [BZOJ1578] [Usaco2009 Feb]Stock Market 股票市场(DP)

    传送门 可以看出 第一天买,第三天卖 == 第一天买,第二天卖完再买,第三天卖 所以我们只考虑前一天买,后一天卖即可 那么有按天数来划分 f[i][j]表示前i天,共有j元,最大的盈利 第一维可以省去 ...

  2. [TJOI2009]开关 (线段树)

    题目描述 现有N(2 ≤ N ≤ 100000)盏灯排成一排,从左到右依次编号为:1,2,......,N.然后依次执行M(1 ≤ M ≤ 100000)项操作,操作分为两种:第一种操作指定一个区间[ ...

  3. linux文件夹作用

    linux下的文件结构,看看每个文件夹都是干吗用的/bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的基 ...

  4. 标准C程序设计七---01

    Linux应用             编程深入            语言编程 标准C程序设计七---经典C11程序设计    以下内容为阅读:    <标准C程序设计>(第7版) 作者 ...

  5. 使用 ftrace 调试 Linux 内核,第1部分

    ftrace 是 Linux 内核中提供的一种调试工具.使用 ftrace 可以对内核中发生的事情进行跟踪,这在调试 bug 或者分析内核时非常有用.本系列文章对 ftrace 进行了介绍,分为三部分 ...

  6. 关于maven下载慢的问题

    昨天刚接触到maven,尝试在mac上配置maven时发现,下载速度极度缓慢,从网上找了些方法,总算是解决了问题. 修改${maven.home}/conf或者${user.home}/.m2文件夹下 ...

  7. HTML 中 SELECT 选项分组

    <select name="viewType"> <option value selected>选择排序/显示方式</option> <o ...

  8. codevs科技庄园

    /* 因为每一秒只能走一个单位长度,而每走一个单位长度又会消耗一个体力值,如果体力值没有了时间还有也只能按照体力值计算,反之一样,所以V对于时间和体力值取小 cnt记录有桃子的树的个数,node[cn ...

  9. 【flyway】开源的数据库版本管理工具【migration】

    开源的数据库版本管理工具[migration] 记录

  10. 【mac】mac上安装软件,报错 鉴定错误,但是安装包都是好的

    出现这个问题, 原因解析: 不是你的安装包下载出错了或者下载失败了这种原因 而是你在打开这个安装包的时候,一定是让你输入密码,而你的密码没有输入正确 解决方式:重新开始打开这个软件的安装包 如下: 1 ...