Delphi 的消息机制浅探二
这样,如果 TWinControl 对象所创建的窗口收到消息后(形象的说法),会被 Windows 回调 TWinControl
.
FObjectInstance,而 FObjectInstance 会呼叫该对象的 TWinControl
.
MainWndProc 函数。就这样 VCL 完成了对象的消息处理过程与 Windows 要求的回调函数格式差异的转换。注意,在转换过程中,Windows 回调时传递进来的第一个参数 HWND 被抛弃了。因此 Delphi 的组件必须使用 TWinControl
.
Handle (或
protected
中的 WindowHandle) 来得到这个参数。Windows 回调函数需要传回的返回值也被替换为 TMessage 结构中的最后一个字段 Result。
为了使大家更清楚窗口被回调的过程,我把从 DispatchMessage 开始到 TWinControl
.
MainWndProc 被调用的汇编代码(你可以把从 FObjectInstance
.
Code 开始至最后一行的代码看成是一个标准的窗口回调函数):
DispatchMessage(&Msg)
// Application.Run 呼叫 DispatchMessage 通知
// Windows 准备回调
Windows 准备回调 TWinControl
.
FObjectInstance 前在堆栈中设置参数:
push LPARAM
push WPARAM
push UINT
push HWND
push (eip
.
Next) ; 把Windows 回调前下一条语句的地址
; 保存在堆栈中
jmp FObjectInstance
.
Code ; 调用 TWinControl
.
FObjectInstance
FObjectInstance
.
Code 只有一句 call 指令:
call ObjectInstance
.
offset
push eip
.
Next
jmp InstanceBlock
.
Code ; 调用 InstanceBlock
.
Code
InstanceBlock
.
Code:
pop ecx ; 将 eip
.
Next 的值存入 ecx, 用于
; 取 @MainWndProc 和 Self
jmp StdWndProc ; 跳转至 StdWndProc
StdWndProc 的汇编代码:
function
StdWndProc(Window: HWND; Message, WParam:
Longint
;
LParam:
Longint
):
Longint
; stdcall; assembler;
asm
push ebp
mov ebp, esp
XOR
EAX,EAX
xor
eax, eax
PUSH EAX
push eax ; 设置 Message
.
Result :=
0
PUSH LParam ; 为什么 Borland 不从上面的堆栈中直接
push dword ptr [ebp+
$14
] ; 获取这些参数而要重新 push 一遍?
PUSH WParam ; 因为 TMessage 的 Result 是
push dword ptr [ebp+
$10
] ; 记录的最后一个字段,而回调函数的 HWND
PUSH Message ; 是第一个参数,没有办法兼容。
push dword ptr [ebp+
$0c
]
MOV EDX,ESP
mov edx, esp ; 设置 Message 在堆栈中的地址为
; MainWndProc 的参数
MOV EAX,[ECX].
Longint
[
4
]
mov eax, [ecx+
$04
] ; 设置 Self 为 MainWndProc 的隐含参数
CALL [ECX].
Pointer
call dword ptr [ecx] : 呼叫 TWinControl
.
MainWndProc(Self,
; @Message)
ADD ESP,
12
add esp,
$0c
POP EAX
pop eax
end
;
pop ebp
ret
$0010
mov eax, eax
看不懂上面的汇编代码,不影响对下文讨论的理解。
===============================================================================
⊙ 补充知识:TWndMethod 概述
===============================================================================
写这段基础知识是因为我在阅读 MakeObjectInstance(MainWndProc) 这句时不知道究竟传递了什么东西给 MakeObjectInstance。弄清楚了 TWndMethod 类型的含义还可以理解后面 VCL 消息系统中的一个小技巧。
TWndMethod =
procedure
(
var
Message: TMessage)
of
object
;
这句类型声明的意思是:TWndMethod 是一种过程类型,它指向一个接收 TMessage 类型参数的过程,但它不是一般的静态过程,它是对象相关(
object
related)的。TWndMethod 在内存中存储为一个指向过程的指针和一个对象的指针,所以占用
8
个字节。TWndMethod类型的变量必须使用已实例化的对象来赋值。举个例子:
var
SomeMethod: TWndMethod;
begin
SomeMethod := Form1
.
MainWndProc;
// 正确。这时 SomeMethod 包含 MainWndProc
// 和 Form1 的指针,可以用 SomeMethod(Msg)
// 来执行。
SomeMethod := TForm
.
MainWndProc;
// 错误!不能用类引用。
end
;
如果把 TWndMethod变量赋值给虚方法会怎样?举例:
var
SomeMethod: TWndMethod;
begin
SomeMethod := Form1
.
WndProc;
// TForm.WndProc 是虚方法
end
;
这时,编译器实现为 SomeMethod 指向 Form1 对象虚方法表中的 WndProc 过程的地址和 Form1 对象的地址。也就是说编译器正确地处理了虚方法的赋值。调用 SomeMethod(Message) 就等于调用 Form1
.
WndProc(Message)。
在可能被赋值的情况下,对象方法最好不要设计为有返回值的函数(
function
),而要设计为过程(
procedure
)。原因很简单,把一个有返回值的对象方法赋值给 TWndMethod 变量,会造成编译时的二义性。
===============================================================================
⊙ VCL 的消息处理从 TWinControl
.
MainWndProc 开始
===============================================================================
通过对 Application
.
Run、TWinControl
.
Create、TWinControl
.
Handle 和 TWinControl
.
CreateWnd 的讨论,我们现在可以把焦点转向 VCL 内部的消息处理过程。VCL 控件的消息源头就是 TWinControl
.
MainWndProc 函数。(如果不能理解这一点,请重新阅读上面的讨论。)
让我们先看一下 MainWndProc 函数的代码(异常处理的语句被我删除):
procedure
TWinControl
.
MainWndProc(
var
Message: TMessage);
begin
WindowProc(Message);
end
;
TWinControl
.
MainWndProc 以引用(也就是隐含传地址)的方式接受一个 TMessage 类型的参数,TMessage 的定义如下(其中的WParam、LParam、Result 各有 HiWord 和 LoWord 的联合字段,被我删除了,免得代码太长):
TMessage =
packed
record
Msg:
Cardinal
;
WParam:
Longint
;
LParam:
Longint
;
Result:
Longint
);
end
;
TMessage 中并没有窗口句柄,因为这个句柄已经在窗口创建之后保存在 TWinControl
.
Handle 之中。TMessage
.
Msg 是消息的 ID 号,这个消息可以是 Windows 标准消息、用户定义的消息或 VCL 定义的 Control 消息等。WParam 和 LParam 与标准 Windows 回调函数中 wParam 和 lParam 的意义相同,Result 相当于标准 Windows 回调函数的返回值。
注意 MainWndProc 不是虚函数,所以它不能被 TWinControl 的继承类重载。(思考:为什么 Borland 不将 MainWndProc 设计为虚函数呢?)
MainWndProc 中建立两层异常处理,用于释放消息处理过程中发生异常时的资源泄漏,并调用默认的异常处理过程。被异常处理包围着的是 WindowProc(Message)。WindowProc 是 TControl(而不是 TWinControl) 的一个属性(
property
):
property
WindowProc: TWndMethod read FWindowProc
write
FWindowProc;
WindowProc 的类型是 TWndMethod,所以它是一个对象相关的消息处理函数指针(请参考前面 TWndMethod 的介绍)。在 TControl
.
Create 中 FWindowProc 被赋值为 WndProc。
WndProc 是 TControl 的一个函数,参数与 TWinControl
.
MainWndProc 相同:
procedure
TControl
.
WndProc(
var
Message: TMessage); virtual;
原来 MainWndProc 只是个代理函数,最终处理消息的是 TControl
.
WndProc 函数。
那么 Borland 为什么要用一个 FWindowProc 来存储这个 WndProc 函数,而不直接调用 WndProc 呢?我猜想可能是基于效率的考虑。还记得上面 TWndMethod 的讨论吗?一个 TWndMethod 变量可以被赋值为一个虚函数,编译器对此操作的实现是通过对象指针访问到了对象的虚函数表,并把虚函数表项中的函数地址传回。由于 WndProc 是一个调用频率非常高的函数(可能要用“百次/秒”或“千次/秒”来计算),所以如果每次调用 WndProc 都要访问虚函数表将会浪费大量时间,因此在 TControl 的构造函数中就把 WndProc 的真正地址存储在 WindowProc 中,以后调用 WindowProc 将就转换为静态函数的调用,以加快处理速度。
===============================================================================
⊙ TWinControl
.
WndProc
===============================================================================
转了层层弯,到现在我们才刚进入 VCL 消息系统处理开始的地方:WndProc 函数。如前所述,TWinControl
.
MainWndProc 接收到消息后并没有处理消息,而是把消息传递给 WindowProc 处理。由于 WindowProc 总是指向当前对象的 WndProc 函数的地址,我们可以简单地认为 WndProc 函数是 VCL 中第一个处理消息的函数,调用 WindowProc 只是效率问题。
WndProc 函数是个虚函数,在 TControl 中开始定义,在 TWinControl 中被重载。Borland 将 WndProc 设计为虚函数就是为了各继承类能够接管消息处理,并把未处理的消息或加工过的消息传递到上一层类中处理。
这里将消息处理的传递过程和对象的构造函数稍加对比:
对象的构造函数通常会在第一行代码中使用
inherited
语句调用父类的构造函数以初始化父类定义的成员变量,父类也会在构造函数开头调用祖父类的构造函数,如此递归,因此一个 TWinControl 对象的创建过程是 TComponent
.
Create -> TControl
.
Create -> TWinControl
.
Create。
而消息处理函数 WndProc 则是先处理自己想要的消息,然后看情况是否要递交到父类的 WndProc 中处理。所以消息的处理过程是 TWinControl
.
WndProc -> TControl
.
WndProc。
因此,如果要分析消息的处理过程,应该从子类的 WndProc 过程开始,然后才是父类的 WndProc 过程。由于 TWinControl 是第一个支持窗口创建的类,所以它的 WndProc 是很重要的,它实现了最基本的 VCL 消息处理。
TWinControl
.
WndProc 主要是预处理一些键盘、鼠标、窗口焦点消息,对于不必响应的消息,TWinControl
.
WndProc 直接返回,否则把消息传递至 TControl
.
WndProc 处理。
从 TWinControl
.
WndProc 摘抄一段看看:
WM_KEYFIRST
..
WM_KEYLAST:
if
Dragging
then
Exit;
// 注意:使用 Exit 直接返回
这段代码的意思是:如果当前组件正处于拖放状态,则丢弃所有键盘消息。
转载于:https://blog.51cto.com/rosehacker/788736
Delphi 的消息机制浅探二相关推荐
- Delphi 的消息机制浅探三
再看一段: WM_MOUSEFIRST..WM_MOUSELAST: if IsControlMouseMsg(TWMMouse(Message)) then begi ...
- Delphi的对象机制浅探[转载]
Delphi的对象机制浅探 savetime2k@yahoo.com 2004-1-3 前几天开始阅读 VCL 源代码,可是几个基类的继承代码把我看得头大.在大富翁请教了几位仁兄后,我还是对Delph ...
- JS闭包中未使用的引用变量回收机制浅探
缘起与群里贴出的一段sizzle代码: 最后的那段指定为null是否有必要? ---------------------------------------忧郁的分割线------------ siz ...
- Android:Android消息机制整理
文章目录 前言 一.Android消息机制的构成 二.为什么只允许主线程对UI进行更新操作 三.消息机制具体分析 ThreadLocal原理分析 MessageQueue Looper Handler ...
- win32mfc————win32消息机制
文章目录 文章目录 文章目录 前言 一.`消息机制`是什么 二.Windows消息机制 1.产生消息 2.消息传递 3.消息处理 三.基本消息 1.窗口创建消息 2.窗口销毁 3.窗口激活 重要消息 ...
- WPF的消息机制(二)- WPF内部的5个窗口之隐藏消息窗口
原文:WPF的消息机制(二)- WPF内部的5个窗口之隐藏消息窗口 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/powertoolsteam/ar ...
- Windows消息机制学习笔记(二)—— 窗口与线程
Windows消息机制学习笔记(二)-- 窗口与线程 要点回顾 消息从哪里来? 实验一:Spy++捕获消息 实验二:消息捕获 消息到哪里去? 窗口在哪? 实验:分析CreateWindowExW 窗口 ...
- 【腾讯Bugly干货分享】经典随机Crash之二:Android消息机制
为什么80%的码农都做不了架构师?>>> 本文作者:鲁可--腾讯SNG专项测试组 测试工程师 背景 承上经典随机Crash之一:线程安全 问题的模型 好几次灰度top1.top ...
- Handler消息机制(二):一个线程有几个Handler
在消息机制里面,有一个非常重要的东西,那就是Looper,Looper的作用主要是从消息队列里面取出消息交给Handler处理,不过不仅限于此,在这里面还有很多东西值得我们去源码看一看: 1.从Loo ...
最新文章
- Sqlserver中查询存储过程是否包含某些文本
- Python小游戏(小蜜蜂)
- 【Linux】一步一步学Linux——sudo命令(105)
- 小米登录协议分析_小米回应小米11充电头兼容问题
- Java集合之EnumSet
- 水泵怎么做_泳池设备日常怎么维护和保养?
- 技术动态 | 大规模中文概念图谱CN-Probase正式发布
- Oracle运行set autotrace on报错SP2-0618、SP2-0611
- goland 方法注释_goland 设置注释模板的过程图文详解
- linux系统如何管理文件
- websocket 带头部信息请求 header_BeetleX之Websocket协议分析详解
- git操作本地仓库基本使用教程
- 产品经理/总监 面试题及答案
- 【马克思主义基本原理】--第一章--【世界的物质性及发展规律】
- 在尘世间做最好的自己
- git远程代码回滚_git 远程分支回滚
- 饥荒控制台输入没用_饥荒控制台使用教程
- 机器学习中的K-means算法原理与R语言实例
- 学生信息管理系统之查:查询成绩信息流程
- 安装与破解IntelliJ IDEA2017
热门文章
- 华为最新全系列交换机命令手册、配置指南下载
- 遍历Page的Controls集合
- vue 写门户网站_你不得不知道的Vue项目技巧
- Android UI开发第四十一篇——墨迹天气3.0引导界面及动画实现
- VS2010 asp.net web site项目使用log4net
- OSPF 224.0.0.5(AllSPFRouters)和224.0.0.6(AllDRouters)的区别
- 在Android软件开发教学过程中应当注意的事项总结
- jquery选择器详解
- fg jobs bg
- 【转】UINavigationController 直接返回到第一级目录