windows消息机制总结(包括mfc,.Net)

本文深入解析了Windows消息处理过程,包括消息循环、消息分发、消息处理等核心机制,并对比了GetMessage与PeekMessage的区别及用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

无论任何框架怎么包装,其内部消息处理的过程都是类似的,消息处理的过程大概如下:操作系统捕获诸如鼠标单击、键盘按下等消息时会自动识别是那个应用程序的消息【我估计比如鼠标单击,根据当前单击的位置判断是那个应用程序的窗口】,然后把消息放到这个应用程序的进程的消息队列中(每个应用程序都有一个消息队列),每个应用程序中都有一个main函数,此函数中有一个While循环,不断的从消息队列中取出消息(一个结构体的实例),紧接着执行TranslateMessage转换消息(用来把虚拟键消息转换为字符消息),然后紧接着执行DispatchMessage分发消息(DispatchMessage函数会在内部调用操作系统函数判断到底是那个窗体或控件的消息,然后发送消息)【我估计这个在内部的操作系统函数是根据鼠标单击的位置判断是单击的那个窗体或最前面的控件】。注意每个控件内部都有一个消息处理函数WndProc(即一个Switch-Case结构),DispatchMessage会根据情况自动调用对应控件的WndProc消息处理函数。

 

下面是转自网页的部分内容,帮助深入理解:

一、揭开.NET消息循环的神秘面纱,转自:http://www.sudu.cn/info/html/edu/20071025/318789.html

 曾经在Win32平台下奋战的程序员们想必记得,为了弄清楚“消息循环”的概念,度过多少不眠之夜。尽管如今在应用程序代码的编写过程中,我们已经不再需要它,但是深刻理解Windows平台内部的消息流转机制依然必要..

  在早年直接用Win32/Win16 API写程序的时代,消息循环是我们必须搞懂的第一个观念。现在,不管你用是Windows上面的哪一套Application Framework(MFC、VCL、VB、.NET Framework),甚至Unix、Linux、MacOSX上面的Application Framework,都不太容易看到消息循环。事实上,消息循环依然存在,只是被这些ApplicationFramework包装起来,深深地埋藏在某个角落。

  本文章试图唤起大家对于消息循环的回忆,也试图解释消息循环如何被封装进.NET Framework的Windows Forms中。虽然Windows Forms将这一切都藏起来,但是也留下许多空间,让我们可以自行处理Win32的消息。

  传统的Windows 程序

  传统的Windows程序,只利用Win32 API撰写,下面是一个程序范例,为了节省篇幅,我将其中许多程序代码省略:

// 程序进入点

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPTSTR lpCmdLine, int nCmdShow){
 MSG msg;
 if (!InitInstance (hInstance, nCmdShow)){
  return FALSE;
 }

 // 主消息循环:

 while (GetMessage(&msg, NULL, 0, 0)){
  TranslateMessage(&msg); DispatchMessage(&msg);
 }
 return (int) msg.wParam;
}

// 函数: WndProc(HWND, unsigned, WORD, LONG)
// 用途: 处理主窗口的消息。

LRESULT CALLBACK WndProc(
 HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  int wmId, wmEvent; PAINTSTRUCT ps;
  HDC hdc;
  switch (message){
   case WM_COMMAND:
    wmId = LOWORD(wParam);
    wmEvent = HIWORD(wParam);
    // 剖析菜单选取项目:
    switch (wmId){
     case IDM_ABOUT:
      DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX,hWnd, (DLGPROC)About);
      break;
     case IDM_EXIT:
      DestroyWindow(hWnd);
      break;
     default:
      return DefWindowProc(hWnd, message,wParam,lParam);
    }
    break;
   case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);

    // TODO: 在此加入任何绘图程序代码...
    EndPaint(hWnd, &ps);
    break;

   case WM_DESTROY:
    PostQuitMessage(0);
    break;
   default:
    return DefWindowProc(hWnd, message,wParam, lParam);
  }
  return 0;
 }

 // [关于] 方块的消息处理例程。

 LRESULT CALLBACK About(HWND hDlg, UINT message,

 WPARAM wParam, LPARAM lParam){
  switch (message){
   case WM_INITDIALOG:
    return TRUE;
   case WM_COMMAND:
    if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){
     EndDialog(hDlg, LOWORD(wParam));
     return TRUE;
    }
    break;
   }
   return FALSE;
  }


  1、从_tWinMain内,程序进入主消息循环;

  2、消息循环从消息队列(Message Queue)中取得一个消息(透过调用GetMessage())。每个执行中的程序都有一个属于自己的消息队列;

  3、消息循环根据消息内容来决定消息应该送给哪个Windows Procedure(WndProc),.. 这就称为消息分发(Message Dispatch)。通常“每一种”窗口或控件(control)都有一个Windows Procedure,来处理该种窗口/控件的行为;

  4、Windows Procedure根据消息内容来决定应该调用哪个函数(利用Switch/Case语法);..

  5、Windows Procedure处理完,控制权回到消息循环。继续进行2、3、4、5的动作;

  6、当消息队列为空的时候,GetMessage()无法取得任何消息,就会进入Idle(空闲)状态,进入睡眠状态(而不是Busy Waiting)。当消息队列不再为空的时候,程序会自动醒过来,继续进行2、3、4、5的动作;

  7、当取得的消息是WM_QUIT,GetMessage()就会得到0的返回值,因而离开消息循环,程序结束。程序会利用调用PostQuitMessage()来将WM_QUIT放置进消息队列中,来造成稍后结束,而不会直接贸然跳离开循环来结束。

  虽名为队列(queue),.. 但是消息队列中的消息并非总是先进先出(First In First Out,FIFO),有一些特例:

  . 只要消息队列中有WM_QUIT ,就会先取出WM_QUIT,导致程序结束。

  . 只有在没有其它消息的时候,WM_PAINT 和WM_TIMER才会被取出。且多个WM_PAINT可能会被合并成一个,WM_TIMER也是如此。

  . 利用TranslateMessage()来处理消息,可能会造成新消息的产生。例如:TranslateMessage()可以辨识出WM_KEYDOWN(按键按下)加上WM_KEYUP(按键放开)就产生WM_CHAR(字符输入)。

  何谓消息

  鼠标移动、按键被按下、窗口被关闭.,这些都会产生消息。在Windows操作系统中,消息是以下面的数据结构存在的(定义在WinUser.h档案中):..

typedef struct tagMSG {
 HWND hwnd;
 UINT message;
 WPARAM wParam;
 LPARAM lParam;
 DWORD time;
 POINT pt;
} MSG;


  消息内有六个信息,分别是:

  . hwnd:窗口/控件的唯一hwnd的编号。消息循环会根据此信息,将消息送到正确目标。

  . message:Windows预先定义的消息种类的ID。

  . wParam 与lParam:有些message本身需要携带更多的信息,这些信息就放在wParam与lParam中。

  . time与pt:消息发生当时的时间与鼠标位置。

  .NET Framework如何封装消息循环

  .NET Framework的Windows Forms将消息循环封装起来,以方便我们使用。本节中所提到的类(class),都是属于System.Windows.Forms名字空间(namespace)。

  简单归纳如下:消息循环被封装进了Application类的Run()静态方法中;Windows Procedure被封装进了NativeWindow 与Control 类中;个别的消息处理动作被封装进Control 类的OnXyz()(例如OnPaint())。我们可以覆盖(override)OnXyz(),来提供我们自己的程序。也可以利用.NET的事件(event)机制,在Xyz事件上,加入我们的事件处理函数(Event Handler)。Control类的OnXyz()会主动调用Xyz 事件的处理函数。

  请注意,因为Xyz 的事件处理函数是由Control类的OnXyz()方法所调用的,所以当你覆写OnXyz()方法时,不要忘了调用Control类的OnXyz()(除非你有特殊需求),否则Xyz事件处理函数将会没有作用。只要调用base.OnXyz(),就可以调用到Control类的OnXyz()方法,如下所示:

protected override void OnPaint(PaintEventArgs e){
 base.OnPaint (e);// TODO: 加入 Form1.OnPaint 实作
}


  我们可以利用覆写Control类的OnXyz(),来决定该消息发生时要做些什么。同理,我们甚至可以覆写Control与NativeWindow类的WndProc(),来定义Windows Procedure。

  再次提醒你,因为OnXyz()系列方法是由Control类的WndProc()所调用的,所以当你覆写WndProc()时,不要忘了调用Control类的WndProc()(除非你有特殊需求),否则OnXyz()系列方法(以及Xyz事件处理函数)将会没有作用。只要调用base.WndProc(),就可以调用到Control类的WndProc(),如下所示:

protected override void WndProc(ref Message m){
 base.WndProc (ref m);//TODO: 加入Form1.WndProc 实作
}


  你可能也注意到了,WndProc()需要一个Message类的参数,这正是MSG被封装成.NET版本的结果。

  一个Windows Forms的范例

  为了让读者更加了解实际的状况,我用下面的实例范例作说明:

namespace WindowsApplication1{
 /// Form1 的摘要描述。
 public class Form1 : Form{
  /// 设计工具所需的变数。
  private Container components = null;
  public Form1(){
   AutoScaleBaseSize = new Size(5, 15);
   ClientSize = new Size(2Array2, 266);
   Name = "Form1";
   Text = "Form1";
   Paint += new PaintEventHandler(this.Form1_Paint);
   Paint += new PaintEventHandler(this.Form1_Paint2);
  }
  /// 应用程序的主进入点。
  [STAThread]
  static void Main(){
   Application.Run(new Form1());
  }
  protected override void OnPaint(PaintEventArgs e){
   base.OnPaint (e); // 2
  }
  private void Form1_Paint(object sender, PaintEventArgs e){
   // 3
  }
  private void Form1_Paint2(object sender, PaintEventArgs e){
   // 4
  }
  protected override void WndProc(ref Message m){
   base.WndProc (ref m); // 1
  }
 }
}


  1、在Main()中,利用Application.Run()来将Form1窗口显示出来,并进入消息循环。程序的执行过程中,Application.Run()一直未结束。

  2、OS在此Process的消息队列内放进一个WM_PAINT消息,好让窗口被显示出来。

  3、WM_PAINT被Application.Run()内的消息循环取出来,分发到WndProc()。由于多态(Polymorphism)的因素,此次调用(invoke)到的WndProc()是属于Form1的WndProc(),也就是上述程序中批注(comment)1的地方,而不是调用到 Control.WndProc()。

  4、在Form1.WndProc()的最后,有调用base.WndProc(),这实际上调用到Control.WndProc()。

  5、Control.WndProc()从Message参数中得知此消息是WM_PAINT,于是调用OnPaint()。由于多态的因素,此次调用到的OnPaint()是属于Form1的OnPaint(),也就是上述程序中批注2的地方,而不是调用到 Control.OnPaint()。

  6、在Form1.OnPaint()的最后,有调用base.OnPaint(),这实际上调用到Control.OnPaint()。

  7、我们曾经在Form1的构造函数(constructor)中将Form1_Paint()与Form1_Paint2()登记成为Paint事件处理函数
(Event Handler)。Control.OnPaint()会去依序去调用这两个函数,也就是上述程序中批注3与4的地方。

  干嘛知道这么多?拜工具之赐,现在的程序员很幸福,可以在糊里胡涂的情况下写出程序来。不过这样的程序员恐怕竞争力不强,毕竟将组件(component)拖放(drag and drop)到画面上,再设定组件属性的工作,称不上有太大的难度。只有深入了解内部原理,才能让自己对技术融会贯通,也才能让程序员之路走得更稳健、更长久。

 

 

二、GetMessage和PeekMessage的联系与区别以及用法 TranslateMessage与DispatchMessage

转自:http://blog.sina.com.cn/s/blog_8a7012cf010151cr.html

联系:

在Windows的内部,GetMessage和PeekMessage执行着相同的代码,Peekmessage和Getmessage都是向系统的消息队列中取得消息,并将其放置在指定的结构。

区别:
PeekMessage: 有消息时返回TRUE,没有消息返回FALSE    
GetMessage:有消息时且消息不为 WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。
GetMessage 取得消息后,删除除WM_PAINT消息以外的消息。
PeekMessage:取得消息后,根据 wRemoveMsg参数判断是否删除消息。PM_REMOVE则删除,PM_NOREMOVE不删除。
The PeekMessage function normally does not remove WM_PAINT messages from the queue. WM_PAINT messages remain in the queue until they are processed. However, if a WM_PAINT message has a null update region, PeekMessage does remove it from the queue.
不能用PeekMessage从消息队列中删除WM_PAINT消息,从队列中删除WM_PAINT消息可以令窗口显示区域的失效区域变得有效(刷新窗口),如果队列中包含 WM_PAINT消息程序就会一直while循环了。

用法:
原型:BOOL GetMessage(
  LPMSG lpMsg,         // address of structure with message
  HWND hWnd,           // handle of window
  UINT wMsgFilterMin,   // first message
  UINT wMsgFilterMax   // last message
);
while ( GetMessage(&msg, NULL, 0, 0))        
{        
    TranslateMessage (&msg);        
    DispatchMessage (&msg);        
}      
return msg.wParam;

原型:
BOOL PeekMessage(
  LPMSG lpMsg,         // pointer to structure for message
  HWND hWnd,           // handle to window
  UINT wMsgFilterMin,   // first message
  UINT wMsgFilterMax,   // last message
  UINT wRemoveMsg       // removal flags
);
while (TRUE)
{        
    if ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))        
    {        
            if (msg.message == WM_QUIT)        
                    break;        
            TranslateMessage (&msg);        
            DispatchMessage (&msg);        
    }        
    else        
    {        
            // 处理空闲任务        
    }        
}        
return msg.wParam;

TranslateMessage(转换消息):
用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。
TranslateMessage函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。
也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。

DispatchMessage(分派消息):
把 TranslateMessage转换的消息 发送到窗口的消息处理函数,此函数在窗口注册时已经指定。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值