由vcl代码理解vcl的消息机制

上传人:第*** 文档编号:32891092 上传时间:2018-02-13 格式:DOC 页数:29 大小:62.50KB
返回 下载 相关 举报
由vcl代码理解vcl的消息机制_第1页
第1页 / 共29页
由vcl代码理解vcl的消息机制_第2页
第2页 / 共29页
由vcl代码理解vcl的消息机制_第3页
第3页 / 共29页
由vcl代码理解vcl的消息机制_第4页
第4页 / 共29页
由vcl代码理解vcl的消息机制_第5页
第5页 / 共29页
点击查看更多>>
资源描述

《由vcl代码理解vcl的消息机制》由会员分享,可在线阅读,更多相关《由vcl代码理解vcl的消息机制(29页珍藏版)》请在金锄头文库上搜索。

1、Delphi,一个非常优秀的开发工具,拥有强大的可视化开发环境、面向组件的快速开发模式、优秀的 VCL类库、快速的代码编译器、强大的数据库和 WEB 开发能力、还有众多的第三方控件支持.(此处省略 x千字,既然大家都知道了,不浪费口水了 _)说到 VCL 的优秀就不能不提到其对 Windows 消息及 API 的较全面和完美的封装,正因为如此开发者在大多数情况下甚至不需理会 Windows 消息处理的细节,而只需要写几行事件驱动代码即可!但如果做为开发人员你还是想对此做些了解的话,那么就继续,通过 VCL 代码本身来体会 VCL 中的消息处理机制。(以下代码取自 Delphi 6)说到 VCL

2、 中的消息处理就不能不提到 TApplication,Windows 会为每一个当前运行的程序建立一个消息队列,用来完成用户与程序的交互,正是通过 Application 完成了对 Windows 消息的集中处理!首先通过 Application.Run 进入消息循环进行消息的处理,其中调用了 HandleMessage。procedure TApplication.HandleMessage;varMsg: TMsg;beginif not ProcessMessage(Msg) then Idle(Msg);/这里先调用 ProcessMessage 处理,返回值为 False调 用 Id

3、le,就是在空闲时,即消息队列中无消息等待处理时调用 Idle。end;function TApplication.ProcessMessage(var Msg: TMsg): Boolean;varHandled: Boolean;beginResult := False;if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then/查询消息队列中有无消息等待处理,参数PM_REMOVE 使消息在处理完后会被删除。beginResult := True;if Msg.Message WM_QUIT then/如果是 WM_QUIT,终止进程,否则执行下面的代码be

4、ginHandled := False;if Assigned(FOnMessage) then FOnMessage(Msg, Handled);if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) andnot IsKeyMsg(Msg) and not IsDlgMsg(Msg) thenbeginTranslateMessage(Msg);/将记录 Msg 传递给 Windows 进行转换DispatchMessage(Msg);/将记录 Msg 回传给 Windowsend;endelse FTerminate :=

5、True;end;end;然后程序中的各个 VCL 对象又是如何接收到 Windows 消息的呢?这还要从窗体的创建开始!首先找到 TWinControl.CreateWnd 中的Windows.RegisterClass(WindowClass)/调用 RegisterClass 注册一个窗体类向上看WindowClass.lpfnWndProc := InitWndProc;/这里指定了窗口的消息处理函数的指针为InitWndProc!再找到 function InitWndProc(HWindow: HWnd; Message, WParam, LParam: Longint): Lon

6、gint;发现了CreationControl.FHandle := HWindow;SetWindowLong(HWindow, GWL_WNDPROC,Longint(CreationControl.FObjectInstance);没有?原来 InitWndProc 初次被调用时候,又使用 API 函数 SetWindowLong 指定处理消息的窗口过程为FObjectInstance。回到 TWinControl.CreateFObjectInstance := Classes.MakeObjectInstance(MainWndProc);找到关键所在了,也许有些朋友对 MakeOb

7、jectInstance 这个函数很熟了,它的作用就是将一个成员过程转换为标准过程。绕了个圈子?为什么呢?很简单,因为窗体成员过程包括一隐含参数传递 Self 指针,所以需要转化为标准过程。constInstanceCount = 313;/这个不难理解吧?314*13+10=4092,再大的话,记录 TInstanceBlock 的大小就超过了下面定义的 PageSizetypePObjectInstance = TObjectInstance;TObjectInstance = packed record Code: Byte;Offset: Integer;case Integer of

8、0: (Next: PObjectInstance);1: (Method: TWndMethod);end;typePInstanceBlock = TInstanceBlock;TInstanceBlock = packed recordNext: PInstanceBlock;Code: array1.2 of Byte;WndProcPtr: Pointer;Instances: array0.InstanceCount of TObjectInstance;end;varInstBlockList: PInstanceBlock;InstFreeList: PObjectInstan

9、ce;function StdWndProc(Window: HWND; Message, WParam: Longint;LParam: Longint): Longint; stdcall; assembler;asmXOR EAX,EAXPUSH EAXPUSH LParamPUSH WParamPUSH MessageMOV EDX,ESP ;将堆栈中构造的记录 TMessage 指针赋给 EDX MOV EAX,ECX.Longint4 ;传递 Self 指针给 EAX,类中的 Self 指针也就是指向 VMT 入口地址CALL ECX.Pointer ;调用 MainWndProc

10、 方法ADD ESP,12POP EAXend;function CalcJmpOffset(Src, Dest: Pointer): Longint;beginResult := Longint(Dest) - (Longint(Src) + 5);end;function MakeObjectInstance(Method: TWndMethod): Pointer;constBlockCode: array1.2 of Byte = ($59, POP ECX $E9); JMP StdWndProc PageSize = 4096;varBlock: PInstanceBlock;In

11、stance: PObjectInstance;beginif InstFreeList = nil thenbeginBlock := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);/分配虚拟内存,并指定这块内存为可读写并可执行Block.Next := InstBlockList;Move(BlockCode, Block.Code, SizeOf(BlockCode);Block.WndProcPtr := Pointer(CalcJmpOffset(Block.Code2, StdWndProc);Ins

12、tance := Block.Instances;repeatInstance.Code := $E8; CALL NEAR PTR Offset Instance.Offset := CalcJmpOffset(Instance, Block.Code);Instance.Next := InstFreeList;InstFreeList := Instance;Inc(Longint(Instance), SizeOf(TObjectInstance);until Longint(Instance) - Longint(Block) = SizeOf(TInstanceBlock);Ins

13、tBlockList := Block;end;Result := InstFreeList;Instance := InstFreeList;InstFreeList := Instance.Next;Instance.Method := Method;end;(注:上面出现的那些 16 进制代码其实就是些 16 进制的机器代码 $59=Pop ECX $E8=Call $E9=Jmp)以上代码看起来有点乱,但综合起来看也很好理解!MakeObjectInstance 实际上就是构建了一个 Block链表其结构看看记录 TInstanceBlock 的结构可知其结构如下:Next/下一页指针

14、Code/Pop ECX 和 JmpWndProcPtr/和 StdWndProc 间的地址偏移Instances/接下来是 314 个 Instance 链表Instance 链表通过记录 TObjectInstance 也很好理解其内容Code/CallOffset/地址偏移Method/指向对象方法的指针(结合 TMethod 很好理解 TWndMethod 这类对象方法指针指向数据的结构)好现在来把这个流程回顾一遍,Windows 回调的是什么呢?其实是转到并执行一段动态生成的代码:先是执行 Call offset ,根据偏移量转去执行 Pop ECX,当然由于在 Call 这之前会将

15、下一条指令入栈,所以这里弹出的就是指向对象方法的指针。接下来就是执行 jmp StdWndProc,其中将堆栈中构造的记录TMessage 指针赋给了 EDX,而根据上面的解释结合 TMethod 去理解,很容易理解MOV EAX,ECX.Longint4 ;传递 Self 指针给 EAX,类中的 Self 指针也就是指向 VMT 入口地址CALL ECX.Pointer ;调用 MainWndProc 方法现在终于豁然开朗了,Windows 消息就是这样被传递到了 TWinControl.MainWndProc,相比 MFC 中的回调全局函数 AfxWndProc 来根据窗体句柄检索对应的对

16、象指针的方法效率要高的多!VCL 比 MFC 优秀的又一佐证! _现在终于找到了 VCL 接收消息的方法 MainWndProcprocedure TWinControl.MainWndProc(var Message: TMessage);begintrytryWindowProc(Message);/由于 TControl 创建实例时已经将 FWindowProc 指向 WndProc,所以这里实际也就是调用 WndProcfinallyFreeDeviceContexts;FreeMemoryContexts;/调用 FreeDeviceContexts 和 FreeMemoryContexts 是为了保证 VCL 线程安全end;exceptApplication.HandleException(Self);end;end;这里也不能忽略了 TWinCo

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 办公文档 > 其它办公文档

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