delphi VCL研究之消息分发机制(转)

原文来源,http://blog.csdn.net/sushengmiyan/article/details/8635550

1.VCL 概貌

先看一下VCL类图的主要分支,如图4.1所示。
在图中可以看到,TObject是VCL的祖先类,这也是Object Pascal语言所规定的。但实际上,TObject以及TObject声明所在的system.pas整个单元,包括在“编译器魔法”话题中提到的_ClassCreate等函数,都是编译器内置支持的。因此,无法修改、删除system.pas中的任何东西,也无法将system.pas加入你的project,否则会得到“Identifier redeclared ‘system’”的错误提示,因project中已经被编译器自动包含了system单元。
意思是,TObject是Object Pascal语言/编译器本身的一个性质!

TObject封装了Object Pascal类/对象的最基本行为。
TPersistent派生自TObject,TPersistent使得自身及其派生类对象具有自我保存、持久存在的能力。
TComponent派生自TPersistent,这条分支之下所有的类都可以被称为“组件”。组件的一般特性是:
(1)可出现在开发环境的“组件板”上。

(2)能够拥有和管理其他组件。

(3)能够存取自身(这是因为TComponent派生自TPersistent)。
TControl派生自TComponent,其分支之下所有的类,都是在运行时可见的组件。
TWinControl派生自TControl,这个分支封装了Windows系统的屏幕对象,也就是一个真正的Windows窗口(拥有窗口句柄)。
TCustomControl派生自TwinControl。从TCustomControl开始,组件拥有了Canvas(画布)属性。

2.TObject与消息分发

首先来看一下TObject这个“万物之源”究竟长得何等模样。它的声明如下:

 1 TObject = class
 2   constructor Create;
 3   procedure Free;
 4   class function InitInstance(Instance: Pointer): TObject;
 5   procedure CleanupInstance;
 6   function ClassType: TClass;
 7   class function ClassName: ShortString;
 8   class function ClassNameIs(const Name: string): Boolean;
 9   class function ClassParent: TClass;
10   class function ClassInfo: Pointer;
11   class function InstanceSize: Longint;
12   class function InheritsFrom(AClass: TClass): Boolean;
13   class function MethodAddress(const Name: ShortString): Pointer;
14   class function MethodName(Address: Pointer): ShortString;
15   function FieldAddress(const Name: ShortString): Pointer;
16   function GetInterface(const IID: TGUID; out Obj): Boolean;
17   class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
18   class function GetInterfaceTable: PInterfaceTable;
19   function SafeCallException(ExceptObject: TObject;
20     ExceptAddr: Pointer): HResult; virtual;
21   procedure AfterConstruction; virtual;
22   procedure BeforeDestruction; virtual;
23   procedure Dispatch(var Message); virtual;
24   procedure DefaultHandler(var Message); virtual;
25   class function NewInstance: TObject; virtual;
26   procedure FreeInstance; virtual;
27   destructor Destroy; virtual;
28 end;  

Delphi Code

       从TObject的声明中可以看到,TObject包含了诸如实例初始化、实例析构、RTTI、消息分发等相关实现的方法。现在就来研究一下TObject与消息分发,这也是VCL对Windows消息封装的模型基础。
      在TObject类中,有一个Dispatch()方法和一个DefaultHandler()方法,它们都是与消息分发机制相关的。
Dispatch()负责将特定的消息分发给合适的消息处理函数。首先它会在对象本身类型的类中寻找该消息的处理函数,如果找到,则调用它;如果没有找到而该类覆盖了TObject的DefaultHandler(),则调用该类的DefaultHandler();如果两者都不存在,则继续在其基类中寻找,直至寻找到TObject这一层,而TObject已经提供了默认的DefaultHandler()方法。
      先来看一个示例程序,它演示了消息分发及处理的过程。
首先自定义一个消息结构TMyMsg,它是我们自定义的消息记录类型。对于自定义的消息类型,VCL只规定它的首4字节必须是消息编号,其后的数据类型任意。同时,VCL也提供了一个TMessage类型用于传递消息。在此程序中,不使用TMessage,而用TMyMsg代替:

1 type
2     TMyMsg = record // 自定义消息结构
3         Msg : Cardinal; // 首4字节必须是消息编号
4         MsgText : ShortString; // 消息的文字描述
5     end; 

TMyMsg记录类型的第2个域我们定义为MsgText,由该域的字符串来给出对这个消息的具体描述信息。当然,这些信息都是由消息分发者给出的。
然后,定义一个类,由它接受外界发送给它的消息。这个类可以说明这个演示程序的核心问题。

TMsgAccepter = class // 消息接收器类
  private
      // 编号为2000的消息处理函数
      procedure AcceptMsg2000(var msg : TMyMsg); message 2000;
      // 编号为2002的消息处理函数
      procedure AcceptMsg2002(var msg : TMyMsg); message 2002;
  public
      procedure DefaultHandler(var Message); override; //默认处理方法
end;

在Object Pascal中,指明类的某个方法为某一特定消息的处理函数,则在其后面添加message关键字与消息值,以此来通知编译器。正如上面类定义中的

procedure AcceptMsg2000(var msg : TMyMsg); message 2000; 

指明AcceptMsg2000()方法用来处理值为2000的消息,该消息以及参数将通过msg参数传递给处理函数。
TMsgAccepter类除提供了值为2000和2002的两个消息的处理函数外,还提供了一个默认的消息处理方法DefaultHandler()。该方法是在TObject中定义的虚方法,而在TMsgAccepter类中覆盖(override)了该方法,重新给出了新的实现。
TMyMsg结构声明与TMsgAccepter类的声明与实现都被定义在MsgDispTest单元中。完整的单元代码如下,请参看其中的TMsgAccepter类的各方法的实现:

unit MsgDispTest;

interface
    uses Dialogs, Messages;
type
    TMyMsg = record
        Msg : Cardinal;
        MsgText : ShortString;
    end;

  TMsgAccepter = class // 消息接收器类
    private
        procedure AcceptMsg2000(var msg : TMyMsg); message 2000;
        procedure AcceptMsg2002(var msg : TMyMsg); message 2002;
    public
        procedure DefaultHandler(var Message); override; //默认处理函数
    end;

implementation
{ TMsgAccepter }

procedure TMsgAccepter.AcceptMsg2000(var msg: TMyMsg);
begin
    ShowMessage(‘嗨,我收到了编号为 2000 的消息,它的描述是:‘ + msg.MsgText);
end;
procedure TMsgAccepter.AcceptMsg2002(var msg: TMyMsg);
begin
    ShowMessage(‘嗨,我收到了编号为2002的消息,它的描述是:‘ + msg.MsgText);
end;
procedure TMsgAccepter.DefaultHandler(var message);
begin
    ShowMessage(‘嗨,这个消息我不认识,无法接收,它的描述是:‘ +
    TMyMsg(message).MsgText);
end;

end.

Delphi Code

接着就是界面代码,我们在Application的主Form(Form1)上放入3个按钮,程序界面如图4.2所示。
界面上的3个按钮的名字分别是:btnMsg2000、btnMsg2001、btnMsg2002。该3个按钮用来分发3个消息,将3个消息的值分别定义为2000、2001和2002。
在Form的OnCreate事件中,创建一个TMsgAccepter类的实例。然后,在3个按钮的OnClick事件中分别加上代码,将3个不同的消息分发给TMsgAccepter类的实例对象,以观察TMsgAccepter作出的反应。最后,在Form的OnDestroy事件中,析构TMsgAccepter类的实例对象。完整的界面程序单元代码如下:

 1 unit Unit1;
 2
 3 interface
 4
 5 uses
 6     Windows, Messages, SysUtils, Variants, Classes, Graphics,
 7     Controls,Forms, Dialogs, StdCtrls, MsgDispTest;
 8
 9 type
10     TForm1 = class(TForm)
11         btnMsg2000: TButton;
12         btnMsg2001: TButton;
13     btnMsg2002: TButton;
14     Label1: TLabel;
15     procedure FormCreate(Sender: TObject);
16     procedure FormDestroy(Sender: TObject);
17     procedure btnMsg2000Click(Sender: TObject);
18     procedure btnMsg2002Click(Sender: TObject);
19     procedure btnMsg2001Click(Sender: TObject);
20     end;
21
22 var
23     Form1: TForm1;
24     MsgAccept : TMsgAccepter; // 自定义的消息接收类
25
26 implementation
27 {$R *.dfm}
28
29 procedure TForm1.FormCreate(Sender: TObject);
30 begin
31     // 创建TMsgAccepter类的实例
32     MsgAccept := TMsgAccepter.Create();
33 end;
34 procedure TForm1.FormDestroy(Sender: TObject);
35 begin
36     // 析构TMsgAccepter类的实例
37     MsgAccept.Free();
38     MsgAccept := nil;
39 end;
40 procedure TForm1.btnMsg2000Click(Sender: TObject);
41 var
42     Msg : TMyMsg;
43 begin
44     // 将值为2000的消息分发给MsgAccept对象,观察其反应
45     Msg.Msg := 2000;
46     Msg.MsgText := ‘Message 2000‘; // 消息的文字描述
47     MsgAccept.Dispatch(Msg); // 分发消息
48 end;
49 procedure TForm1.btnMsg2002Click(Sender: TObject);
50 var
51     Msg : TMyMsg;
52 begin
53     // 将值为2002的消息分发给MsgAccept对象,观察其反应
54     Msg.Msg := 2002;
55     Msg.MsgText := ‘Message 2002‘; // 消息的文字描述
56     MsgAccept.Dispatch(Msg); // 分发消息
57 end;
58 procedure TForm1.btnMsg2001Click(Sender: TObject);
59 var
60     Msg : TMyMsg;
61 begin
62     // 将值为2001的消息分发给MsgAccept对象,观察其反应
63     Msg.Msg := 2001;
64     Msg.MsgText := ‘Message 2001‘; // 消息的文字描述
65     MsgAccept.Dispatch(Msg); // 分发消息
66 end;
67
68 end.

在TMsgAccepter类的代码中可以看到,它只能处理编号为2000和2002的消息,而没有编号为2001的消息的处理函数,但它覆盖了TObject的DefaultHandler(),于是就提供了默认的消息处理函数。
运行程序,分别单击3个按钮,得到了3句不同的回答。对于消息2000和2002,TMsgAccepter照单全收,正确识别出所接收到的消息。而只有在接收消息2001时,由于没有提供专门的消息处理函数,导致了对DefaultHandler()的调用。幸运的是,在DefaultHandler中,还可以使用message参数给出的附加信息(TMyMsg记录类型中的MsgText域)。

时间: 2024-10-13 15:55:20

delphi VCL研究之消息分发机制(转)的相关文章

Cocos2d-x 3.0 屏幕触摸及消息分发机制

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 题外话: 唉. 开学了!    好烦. 这就已经大三了, 两年前的这时候,我还是懵懂的大一小学弟, 两年后.就要奔上社会就业了. 光阴似箭.日月如梭呀~ 正文: 好久没做cocos2d-x了,这次练习一下.屏幕触摸及消息分发机制. 这里,我用的是cocos2d-

Python-RabbitMQ消息分发机制

上一篇中的例子是一个生产者对应一个消费者,那能不能一个生产者对应一个消费者呢? 下面来测试一下,顺便观察一下它的分发策略... 步骤一:先编辑生产者代码(rabbit_send.py) #top1:导入pika模块 import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) import pika #top2:建立socket connection = pika.BlockingConnection(pika.Connectio

Android正在使用Handler实现消息分发机制(两)

在开始这篇文章之前,.首先,我们在总结前两篇文章Handler, Looper和MessageQueue像一些关键点: 0)在创建线程Handler之前,你必须调用Looper.prepare(), 创建一个线程局部变量Looper,然后调用Looper.loop() 进入轮循. 1)当Handler创建之后,就能够调用Handler的sendMessageAtTime方法发送消息.而实际上是调用MessageQueue的enqueueMessage方法.将相应的消息放入消息队列. 2)每个线程

(转)RabbitMQ消息队列(三):任务分发机制

在上篇文章中,我们解决了从发送端(Producer)向接收端(Consumer)发送“Hello World”的问题.在实际的应用场景中,这是远远不够的.从本篇文章开始,我们将结合更加实际的应用场景来讲解更多的高级用法. 当有Consumer需要大量的运算时,RabbitMQ Server需要一定的分发机制来balance每个Consumer的load.试想一下,对于web application来说,在一个很多的HTTP request里是没有时间来处理复杂的运算的,只能通过后台的一些工作线程

Delphi中多线程用消息实现VCL数据同步显示

Delphi中多线程用消息实现VCL数据同步显示 Lanno Ckeeke 2006-5-12 概述: delphi中严格区分主线程和子主线程,主线程负责GUI的更新,子线程负责数据运算,当数据运行完毕后,子线程可以向主线程式发送消息,以便通知其将VCL中的数据更新. 实现: 关键在于消息的发送及接收.在消息结构Tmessage中wParam和lParam类型为Longint,而指针类型也定义为Longint,可以通过此指针来传递自己所感兴趣的数据.如传递字符数组: 数组定义: const MA

RabbitMQ消息队列(三):任务分发机制

在上篇文章中,我们解决了从发送端(Producer)向接收端(Consumer)发送“Hello World”的问题.在实际的应用场景中,这是远远不够的.从本篇文章开始,我们将结合更加实际的应用场景来讲解更多的高级用法. 当有Consumer需要大量的运算时,RabbitMQ Server需要一定的分发机制来balance每个Consumer的load.试想一下,对于web application来说,在一个很多的HTTP request里是没有时间来处理复杂的运算的,只能通过后台的一些工作线程

TMsgThread, TCommThread -- 在delphi线程中实现消息循环(105篇博客,好多研究消息的文章)

在delphi线程中实现消息循环 在delphi线程中实现消息循环 Delphi的TThread类使用很方便,但是有时候我们需要在线程类中使用消息循环,delphi没有提供. 花了两天的事件研究了一下win32的消息系统,写了一个线程内消息循环的测试. 但是没有具体应用过,贴出来给有这方面需求的DFW参考一下.希望大家和我讨论. {----------------------------------------------------------------------------- Unit

Chromium的IPC消息发送、接收和分发机制分析

由于Chromium采用多进程架构,因此会涉及到进程间通信问题.通过前面一文的学习,我们知道Browser进程在启动Render进程的过程中会建立一个以UNIX Socket为基础的IPC通道.有了IPC通道之后,接下来Browser进程与Render进程就以消息的形式进行通信.我们将这种消息称为IPC消息,以区别于线程消息循环中的消息.本文就分析Chromium的IPC消息发送.接收和分发机制. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! Chrom

Android中利用Handler实现消息的分发机制(三)

在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而如果在子线程中需要去使用Handler的时候,我们就需要显式地去调用Looper的 prepare方法和loop方法,从而为子线程创建其唯一的Looper. 具体代码如下: class LooperThread extends Thread { public Handler mHandler; public void run()