标题：windows消息处理机制
什么是消息
消息，就是指Windows发出的一个通知，告诉应用程序某个事情发生了。例如，单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序。消息本身是作为一个记录传递给应用程序的，这个记录（一般在 C/Java/汇编 中称为“结构体”）中包含了消息的类型以及其他信息。例如，对单击鼠标所产生的消息来说，这个记录（结构体）中包含了单击鼠标的消息号（WM_LBUTTONDOWN）、单击鼠标时的坐标(由X,Y值连接而成的一个32位整数)。这个记录类型叫做TMsg。
在Delphi的Windows单元中是这样声明的： 
type 
TMsg = packed record 
hwnd: HWND; / /窗口句柄 
message: UINT; / /消息常量标识符 
wParam: WPARAM ; // 16位消息的特定附加信息 
lParam: LPARAM ; // 32位消息的特定附加信息 
time: DWORD; / /消息创建时的时间 
pt: TPoint; / /消息创建时的鼠标位置 
end ; 
其中TPoint定义是：
TPoint= record
x:integer;
y:integer;
end;
在C语言中的定义是：
typedef struct Msg
{
HWND hwnd; / /窗口句柄 
UINT message; / /消息常量标识符 
WPARAM wParam; // 32位消息的特定附加信息 
LPARAM lParam; // 32位消息的特定附加信息 
DWORD time; / /消息创建时的时间 
TPoint pt; / /消息创建时的鼠标位置 
} TMsg;
typedef struct TPoint
{
int x;
int y;　
}TPoint;
消息中有什么？
hwnd
32位的窗口句柄。窗口可以是任何类型的屏幕对象，因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。 
message
用于区别其他消息的常量值，这些常量可以是Windows单元中预定义的常量，也可以是自定义的常量。 
wParam
通常是一个与消息有关的常量值，也可能是窗口或控件的句柄。 
lParam
通常是一个指向内存中数据的指针。
由于WParam、lParam和Pointer都是32位的，即等同于DWORD。因此，它们之间可以相互转换。
Windows的消息系统
Windows的消息系统是由3个部分组成的： 
· 消息队列。Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取 
消息，然后分派给某个窗口。 
· 消息循环。通过这个循环机制应用程序从消息队列中检索消息，再把它分派给适当的窗口，然 
后继续从消息队列中检索下一条消息，再分派给适当的窗口，依次进行。 
· 窗口过程。每个窗口都有一个窗口过程来接收传递给窗口的消息，它的任务就是获取消息然后 
响应它。窗口过程是一个回调函数；处理了一个消息后，它通常要返回一个值给Windows。 
注意回调函数是程序中的一种函数，它是由Windows或外部模块调用的。 
一个消息从产生到被一个窗口响应，其中有5个步骤： 
1) 系统中发生了某个事件。 
2) Windows把这个事件翻译为消息，然后把它放到消息队列中。 
3) 应用程序从消息队列中接收到这个消息，把它存放在TMsg记录中。 
4) 应用程序把消息传递给一个适当的窗口的窗口过程。 
5) 窗口过程响应这个消息并进行处理。 
步骤3和4构成了应用程序的消息循环。消息循环往往是Windows应用程序的核心，因为消息循环 
使一个应用程序能够响应外部的事件。消息循环的任务就是从消息队列中检索消息，然后把消息传递给适当的窗口。如果消息队列中没有消息，Windows就允许其他应用程序处理它们的消息。
Windows操作系统最大的特点就是其图形化的操作界面，其图形化界面是建立在其消息处理机制这个基础之上的。如果不理解Windows消息处理机制，肯定无法深入的理解Windows编程。可惜很多程序员对Windows消息只是略有所闻，对其使用知之甚少，更不了解其内部实现原理，本文试着一步一步向大家披露我理解的Windows消息机制。可以说，掌握了这一部分知识，就是掌握了Windows编程中的神兵利器，灵活运用它，将会极大的提高我们的编程能力。
消息实现概述
一、Windows消息概述Windows窗体是怎样展现在屏幕上的呢？众所周知，是通过API绘制实现的。Windows操作系统提供了一系列的API函数来实现界面的绘制功能，例如：
DrawText　绘制文字
DrawEdge　绘制边框
DrawIcon　绘制图标
BitBlt 绘制位图
Rectangle　绘制矩形
…
再复杂的程序界面都是通过这些函数来实现的。
那什么时候调用这些函数呢？显然我们需要一个控制中心，用来进行“发号施令”，我们还需要一个命令传达机制，将命令即时的传达到目的地。这个控制中心，就是一个动力源，就像一颗心脏，源源不断地将血液送往各处。这个命令传达机制就是Windows消息机制，Windows消息就好比是身体中的血液，它是命令传达的使者。
Windows消息控制中心一般是三层结构，其顶端就是Windows内核。Windows内核维护着一个消息队列，第二级控制中心从这个消息队列中获取属于自己管辖的消息，后做出处理，有些消息直接处理掉，有些还要发送给下一级窗体(Window)或控件（Control）。第二级控制中心一般是各Windows应用程序的Application对象。第三级控制中心就是Windows窗体对象，每一个窗体都有一个默认的窗体过程，这个过程负责处理各种接收到的消息。如下图所示：
  说明图（注：windows指windows操作系统；窗口：即windows窗口；窗体：包括窗口，以及有句柄的控件；control指控件，控件本身也可能是一个window，也可能不是；Application即应用程序，应用程序也可能不会用到Windows消息机制，这里我们专门讨论有消息循环的应用程序）
消息是以固定的结构传送给应用程序的，结构如下：
Public Type MSG
hwnd As Long
message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
其中hwnd是窗体的句柄，message是一个消息常量，用来表示消息的类型，wParam和lParam都是32位的附加信息，具体表示什么内容，要视消息的类型而定，time是消息发送的时间，pt是消息发送时鼠标所在的位置。
Windows操作系统中包括以下几种消息：
1、标准Windows消息：
这种消息以WM_打头。
2、通知消息
通知消息是针对标准Windows控件的消息。这些控个包括：按钮(Button)、组合框(ComboBox)、编辑框(TextBox)、列表框（ListBox）、ListView控件、Treeview控件、工具条(Toolbar)、菜单（Menu）等。每种消息以不同的字符串打头。
3、自定义消息
编程人员还可以自定义消息。二、关于Windows句柄不是每个控件都能接收消息，转发消息和绘制自身，只有具有句柄（handle）的控件才能做到。有句柄的控件本质上都是一个窗体(window)，它们可以独立存在，可以作为其它控件的容器，而没有句柄的控件，如Label，是不能独立存在的，只能作为窗口控件的子控件，它不能绘制自身，只能依靠父窗体将它绘制来。
句柄的本质是一个系统自动维护的32位的数值，在整个操作系统的任一时刻，这个数值是唯一的。但该句柄代表的窗体释放后，句柄也会被释放，这个数值又可能被其它窗体使用。也就是说，句柄的数值是动态的，它本身只是一个唯一性标识，操作系统通过句柄来识别和查找它所代表的对象。
然而，并非所有的句柄都是窗体的句柄，Windows系统中还中很多其它类型的句柄，如画布（hdc）句柄，画笔句柄，画刷句柄，应用程序句柄(hInstance)等。这种句柄是不能接收消息的。但不管是哪种句柄，都是系统中对象的唯一标识。本文只讨论窗体句柄。
那为什么句柄使窗口具有了如此独特的特性呢？实际是都是由于消息的原因。由于有了句柄，窗体能够接收消息，也就知道了该什么时候绘制自己，绘制子控件，知道了鼠标在什么时候点击了窗口的哪个部分，从而作出相应的处理。句柄就好像是一个人的身份证，有了它，你就可以从事各种社会活动；否则的话，你要么是一个社会看不到的黑户，要么跟在别人后面，通过别人来证明你的存在。三、消息的传送1、从消息队列获取消息：
可以通过PeekMessage或GetMessage函数从Windows消息队列中获取消息。Windows保存的消息队列是以线程(Thread)来分组的，也就是说每个线程都有自己的消息队列。
2、发送消息
发送消息到指定窗体一般通过以下两个函数完成：SendMessage和PostMessage。两个函数的区别在于：PostMessage函数只是向线程消息队列中添加消息，如果添加成功，则返回True,否则返回False，消息是否被处理，或处理的结果，就不知道了。而SendMessage则有些不同，它并不是把消息加入到队列里，而是直接翻译消息和调用消息处理(线程向自己发送消息才是这样)，直到消息处理完成后才返回。所以，如果我们希望发送的消息立即被执行，就应该调用SendMessage。
还有一点，就是SendMessage发送的消息由于不会被加入到消息队列中（错：线程向其他线程发送消息也是追加到其他线程的发送消息队列的，即使这两个线程在同一个进程也是如此），所以通过PeekMessage或GetMessage是不能获取到由SendMessage发送的消息。
另外，有些消息用PostMessage不会成功，比如wm_settext。所以不是所有的消息都能够用PostMessage的。
还有一些其它的发送消息API函数，如PostThreadMessage，SendMessageCallback，SendMessageTimeout，SendNotifyMessage等。四、消息循环与窗体过程消息循环是应用程序能够持续存在的根本原因。如果循环退出，则应用程序就结束了。
我们来看一看Delphi中封装的消息循环是怎样的：
第一步：程序开始运行(Run)
Application.Initialize;　//初始化
Application.CreateForm(TForm1, Form1);　//创建主窗体
Application.Run;　//开始运行，准备进行消息循环
如果不创建主窗体，应用程序同样可以存在和运行。
第二步：开始调用消息循环(HandleMessage)
procedure TApplication.Run;
begin
FRunning := True;
try
AddExitProc(DoneApplication);
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
Repeat　 //注：循环开始
try
HandleMessage;
except
HandleException(Self);
end;
until Terminated;　//循环结束条件
end;
finally
FRunning := False;
end;
end;
第三步：消息循环中对消息的处理。
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;
窗体过程实际上是一个回调函数。所谓的回调函数，实际上就是由Windows操作系统或外部程序调用的函数。回调函数一般都有规定的参数格式，以地址方式传递给调用者。窗口过程中是Windows操作系统调用了，在一个窗口创建的时候，在分配窗体句柄的时候就需要传入回调函数地址。那为什么我们平时编程看不到这个回调函数呢？这是由于我们的编程工具已经为我们生成了默认的窗体过程，这个过程的要做的事情就是判断不同的消息类型，然后做出不同的处理。例如可以为键盘或鼠标输入生成事件等。五、消息与事件事件本质上是对消息的封装，是IDE编程环境为了简化编程而提供的有用的工具。这个封装是在窗体过程中实现的。每种IDE封装了许多Windows的消息，例如：
事件
消息
OnActivate
WM_ACTIVATE
OnClick
WM_XBUTTONDOWN
OnCreate
WM_CREATE
OnDblClick
WM_XBUTTONDBLCLICK
OnKeyDown
WM_KEYDOWN
OnKeyPress
WM_CHAR
OnKeyUp
WIN_KEYUP
OnPaint
WM_PAINT
OnResize
WM_SIZE
OnTimer
WM_TIMER