您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 企业财务 > Delphi 的消息机制浅探
1Delphi的消息机制浅探savetime2k@yahoo.com2004.1.9我从去年12月上旬开始等待李维的《InsideVCL》。我当时的计划是,在这本书的指导下深入学习Delphi。到了12月底,书还没有出来,我不愿再等,开始阅读VCL源代码。在读完TObject、TPersistant和TComponent的代码之后,我发现还是不清楚Delphi对象到底是怎样被创建的。于是我查看Delphi生成的汇编代码,终于理解了对象创建的整个过程(这里要特别感谢book523的帮助)。此后我就开始学习DelphiVCL的消息处理机制。自从我写下《Delphi的对象机制浅探》,至今正好一个星期,我也基本上把DelphiVCL的消息处理框架读完了。我的学习方法就是阅读源代码,一开始比较艰苦,后来线索逐渐清晰起来。在此把自己对DelphiVCL消息机制的理解记录下来,便于今后的复习,也给初学Delphi或没有时间阅读VCL源代码的朋友参考(毕竟没有几个程序员像我这样有时间:)。由于学习时间较短,一定会有错误,请大家指正。我在分析VCL消息机制的过程中,基本上只考查了三个类TObject、TControl和TWinControl。虽然我没有阅读上层类(如TForm)的代码,但我认为这些都是实现的细节。我相信VCL消息系统中最关键的东西都在这三个类中。纲举而目张,掌握基础类的消息处理方法之后再读其他类的消息处理过程就容易得多了。要想读懂本文,最低配置为:了解Win32消息循环和窗口过程基本了解TObject、TControl和TWinControl实现的内容熟悉Delphi对象的重载与多态推荐配置为:熟悉Win32SDK编程熟悉Delphi的对象机制熟悉Delphi内嵌汇编语言推荐阅读:《Delphi的原子世界》《VCL窗口函数注册机制研究手记,兼与MFC比较》=584889《Delphi的对象机制浅探》=2390131本文排版格式为:正文由窗口自动换行;所有代码以80字符为边界;中英文字符以空格符分隔。(作者保留对本文的所有权利,未经作者同意请勿在在任何公共媒体转载。)目录⊙一个GUIApplication的执行过程:消息循环的建立⊙TWinControl.Create、注册窗口过程和创建窗口⊙补充知识:TWndMethod概述⊙VCL的消息处理从TWinControl.MainWndProc开始⊙TWinControl.WndProc⊙TControl.WndProc⊙TObject.Dispatch⊙TWinControl.DefaultHandler2⊙TControl.Perform和TWinControl.Broadcast⊙TWinControl.WMPaint⊙以TWinControl为例描述消息传递的路径正文⊙一个GUIApplication的执行过程:消息循环的建立通常一个Win32GUI应用程序是围绕着消息循环的处理而运行的。在一个标准的C语言Win32GUI程序中,主程序段都会出现以下代码:while(GetMessage(&msg,NULL,0,0))//GetMessage第二个参数为NULL,//表示接收所有应用程序产生的窗口消息{TranslateMessage(&msg);//转换消息中的字符集DispatchMessage(&msg);//把msg参数传递给lpfnWndProc}lpfnWndProc是Win32API定义的回调函数的地址,其原型如下:int__stdcallWndProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam);Windows回调函数(callbackfunction)也通常被称为窗口过程(windowprocedure),本文随意使用这两个名称,代表同样的意义。应用程序使用GetMessage不断检查应用程序的消息队列中是否有消息到达。如果发现了消息,则调用TranslateMessage。TranslateMessage主要是做字符消息本地化的工作,不是关键的函数。然后调用DispatchMessage(&msg)。DispatchMessage(&msg)使用msg为参数调用已创建的窗口的回调函数(WndClass.lpfnWndProc)。lpfnWndProc是由用户设计的消息处理方法。当GetMessage在应用程序的消息队列中发现一条WM_QUIT消息时,GetMessage返回False,消息循环才告结束,通常应用程序在这时清理资源后也结束运行。使用最原始的Win32API编写的应用程序的执行过程是很容易理解的,但是用DelphiVCL组件封装消息系统,并不是容易的事。首先,Delphi是一种面向对象的程序设计语言,不但要把Win32的消息处理过程封装在对象的各个继承类中,让应用程序的使用者方便地调用,也要让VCL组件的开发者有拓展消息处理的空间。其次,Delphi的对象模型中所有的类方法都是对象相关的(也就是传递了一个隐含的参数Self),所以Delphi对象的方法不能直接被Windows回调。DelphiVCL必须用其他的方法让Windows回调到对象的消息处理函数。让我们跟踪一个标准的DelphiApplication的执行过程,查看Delphi是如何开始一个消息循环的。programProject1;beginApplication.Initialize;Application.CreateForm(TForm1,Form1);Application.Run;end.在Project1的Application.Initialize之前,Delphi编译器会自动插入一行3代码:SysInit._InitExe。_InitExe主要是初始化HInstance和模块信息表等。然后_InitExe调用System._StartExe。System._StartExe调用System.InitUnit;System.InitUnit调用项目中所有被包含单元的Initialization段的代码;其中有Controls.Initialization段,这个段比较关键。在这段代码中建立了Mouse、Screen和Application三个关键的全局对象。Application.Create调用Application.CreateHandle。Application.CreateHandle建立一个窗口,并设置Application.WndProc为回调函数(这里使用了MakeObjectInstance方法,后面再谈)。Application.WndProc主要处理一些应用程序级别的消息。我第一次跟踪应用程序的执行时没有发现Application对象的创建过程,原来在SysInit._InitExe中被隐含调用了。如果你想跟踪这个过程,不要设置断点,直接按F7就发现了。然后才到了Project1的第1句:Application.Initialize;这个函数只有一句代码:ifInitProcnilthenTProcedure(InitProc);也就是说如果用户想在应用程序的执行前运行一个特定的过程,可以设置InitProc指向该过程。(为什么用户不在Application.Initialize之前或在单元的Initliazation段中直接运行这个特定的过程呢?一个可能的答案是:如果元件设计者希望在应用程序的代码执行之前执行一个过程,并且这个过程必须在其他单元的Initialization执行完成之后执行[比如说Application对象必须创建],则只能使用这个过程指针来实现。)然后是Project1的第2句:Application.CreateForm(TForm1,Form1);这句的主要作用是创建TForm1对象,然后把Application.MainForm设置为TForm1。最后是Project1的第3句:Application.Run;TApplication.Run调用TApplication.HandleMessage处理消息。Application.HandleMessage的代码也只有一行:ifnotProcessMessage(Msg)thenIdle(Msg);TApplication.ProcessMessage才真正开始建立消息循环。ProcessMessage使用PeekMessageAPI代替GetMessage获取消息队列中的消息。使用PeekMessage的好处是PeekMessage发现消息队列中没有消息时会立即返回,这样就为HandleMessage函数执行Idle(Msg)提供了依据。ProcessMessage在处理消息循环的时候还特别处理了HintMsg、MDIMsg、KeyMsg、DlgMsg等特殊消息,所以在Delphi中很少再看到纯Win32SDK编程中的要区分DialogWindow、MDIWindow的处理,这些都被封装到TForm中去了(其实Win32SDK中的Dialog也是只是Microsoft专门写了一个窗口过程和一组函数方便用户界面的设计,其内部运作过程与一个普通窗口无异)。functionTApplication.ProcessMessage(varMsg:TMsg):Boolean;varHandled:Boolean;begin4Result:=False;ifPeekMessage(Msg,0,0,0,PM_REMOVE)then//从消息队列获取消息beginResult:=True;ifMsg.MessageWM_QUITthenbeginHandled:=False;//Handled表示Application.OnMessage是否已经处理过//当前消息。//如果用户设置了Application.OnMessage事件句柄,//则先调用Application.OnMessageifAssigned(FOnMessage)thenFOnMessage(Msg,Handled);ifnotIsHintMsg(Msg)andnotHandledandnotIsMDIMsg(Msg)andnotIsKeyMsg(Msg)andnotIsDlgMsg(Msg)then//思考:notHandled为什么不放在最前?beginTranslateMessage(Msg);//处理字符转换DispatchMessage(Msg);//调用WndClass.lpfnWndProcend;endelseFTerminate:=True;//收到WM_QUIT时应用程序终止//(这里只是设置一个终止标记)end;end;从上面的代码来看,Delphi应用程序的消息循环机制与标准Win32C语言应用程序差不多。只是Delphi为了方便用户的使用设置了很多扩展空间,其副作用是消息处理会比纯CWin32API调用效率要低一些。⊙TWinControl.Create、注册窗口过程和创建窗口上面简单讨论了一个Application的建立到形成消息循环的过程,现在的问题是Delphi控件是如何封装创建窗口这一过程的。因为只有建立了窗口,消息循环才有意义。让我们先回顾DelphiVCL中几个主要类的继承架框:TObject所有对象的基类TPersistent所有具有流特性对象的基类TComponent所有能放在DelphiFormDesigner上的对象的基类TControl所有可视的对象的基类TWinControl所有具有窗口句柄的对象基类Delphi是从TWinControl开始实现窗口相关的元件。所谓窗口
本文标题:Delphi 的消息机制浅探
链接地址:https://www.777doc.com/doc-3403965 .html