揭开.NET消息循环的神秘面纱(GetMessage()无法取得任何消息,就会进入Idle(空闲)状态,进入睡眠状态(而不是Busy Waiting)。当消息队列不再为空的时候,程序会自动醒过来)

揭开.NET消息循环的神秘面纱(-)

http://hi.baidu.com/sakiwer/item/f17dc33274a04df2a9842866

曾经在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(字符输入)。

揭开.NET消息循环的神秘面纱(二)

http://hi.baidu.com/sakiwer/item/23dea1cc848f1a12b77a2466

何谓消息

  鼠标移动、按键被按下、窗口被关闭.,这些都会产生消息。在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(292, 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)到画面上,再设定组件属性的工作,称不上有太大的难度。只有深入了解内部原理,才能让自己对技术融会贯通,也才能让程序员之路走得更稳健、更长久。

dev.yesky.com/msdn/323/2339323.shtml

http://blog.csdn.net/jiangxinyu/article/details/8080393

时间: 2024-10-10 02:57:17

揭开.NET消息循环的神秘面纱(GetMessage()无法取得任何消息,就会进入Idle(空闲)状态,进入睡眠状态(而不是Busy Waiting)。当消息队列不再为空的时候,程序会自动醒过来)的相关文章

揭开Sass和Compass的神秘面纱

可能之前你像我一样,对Sass和Compass毫无所知,好一点儿的可能知道它们是用来作为CSS预处理的.那么,今天请跟我一起学习下Sass和Compass的一些基础知识,包括它们是什么.如何安装.为什么要使用.基础语法等一些基本知识.需要说明的是我也仅仅只是刚刚接触Sass和Compass,一些高级用法等将不再本文的讨论范围之内.接触一周以后发现Sass和Compass的用处非常大,也打算今后在项目中尝试引进并应用起来.希望读完以后,你跟我一样对Sass和Compass给你带来的东西非常开心,也

揭开移动 APM 的五大神秘面纱

众所周知,移动应用市场总带给人们惊喜,挣钱快.开发门槛低.用户参与感强是移动时代的最大特点.根据 GigaOm 等研究报道,2013年 eBay 3600万新用户中,有40%是移动端用户,总交易额达350亿美元--相比去年增长了88%. 然而,如何在移动端更好地吸引用户则是个非常棘手的问题.因为移动用户非常专注与应用互动,在一个小小的屏幕上触发强烈的交互.用户的指尖面临着多种选择,以至于他们完全无法容忍那些设计不良或效果很差的应用.传言 iTunes 的应用商店共计有120万个应用在线上.And

图文并茂|为你揭开微服务架构的“神秘面纱”!

看到最近"微服务架构"这个概念这么火,作为一个积极上进的程序猿,成小胖忍不住想要学习学习.而架构师老王(不是隔壁老王)最近刚好在做公司基础服务的微服务化研究和落地,对此深有研究. 于是成小胖马上屁颠屁颠的跑过去向老王请教:"王哥,我看微服务架构这么火,我也想学,您给我讲讲啥是微服务架构呗?" 老王笑了笑说:"要想知道什么是微服务架构,你得先知道什么系统架构设计." 成小胖的理想是成为一名架构师,平时积累了不少知识,因此对"系统架构设计&

EF – 4.揭开Entity Framework数据更新的神秘面纱(2)

5.6.4 <DbSet与DbContext> 介绍DbSet与DbContext中的核心属性及重要方法. 5.6.5 <数据更新的奥秘> 这一讲极为重要,因为它揭示出了Entity Framework实现数据更新的内部机理,了解这些内容,对于用好Entity Framework非常重要. 5.6.6 <一对一关联概述> 5.6.7 <一对一关联CRUD演示> 在两讲视频中,首先介绍了数据库中一对一关联表的设计规范,接着通过实例介绍了如何合适Entity F

深入探讨MFC消息循环和消息泵

首先,应该清楚MFC的消息循环(::GetMessage,::PeekMessage),消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情.在MFC的应用程序中(应用程序类基于CWinThread继承),必须要有一个消息循环,他的作用是从应用程序的消息队列中读取消息,并把它派送出去(::DispatchMessage).而消息路由是指消息派送出去之后,系统(USER32.DLL)把消息投递到哪个窗口,以及以后消息在窗口之间的传递是怎样的.  消

理解Windows消息循环机制

理解消息循环和整个消息传送机制对Windows编程十分重要.如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方. 什么是消息(Message)每个消息是一个整型数值,如果查看头文件(查看头文件了解API是一个非常好的习惯和普遍的做法)可以发现如下一些宏定义: #define WM_INITDIALOG                   0x0110 #define WM_COMMAND                      0x0111 #define WM_L

【安全健行】(4):揭开shellcode的神秘面纱

2015/5/18 16:20:18 前面我们介绍了shellcode使用的基本策略,包括基本的shellcode.反向连接的shellcode以及查找套接字的shellcode.在宏观上了解了shellcode之后,今天我们来深入一步,看看shellcode到底是什么.也许大家和我一样,从接触安全领域就听说shellcode,也模糊地知道shellcode基本就是那个攻击载荷,但是shellcode到底长什么样,却一直遮遮掩掩,难睹真容.趁今天这个机会,我们一起来揭开shellcode的神秘面

揭开SAP Fiori编程模型规范里注解的神秘面纱 - @OData.publish

今天是2020年2月1日鼠年大年初八,这是Jerry鼠年的第8篇文章,也是汪子熙公众号总共第207篇原创文章. Jerry的前一篇文章 揭开SAP Fiori编程模型规范里注解的神秘面纱 - @ObjectModel.readOnly工作原理解析,给大家分享了@ObjectModel.readOnly这个注解对应的Fiori UI和ABAP后台的工作原理. 今天我们继续研究另一个注解@OData.publish. 在SAP官网的ABAP Programming Model for SAP Fio

揭开RecyclerView的神秘面纱(二):处理RecyclerView的点击事件

前言 上一篇文章揭开RecyclerView的神秘面纱(一):RecyclerView的基本使用中,主要讲述了RecyclerView的基本使用方法,不同的布局管理器而造成的多样化展示方式,展示了数据之后,一般都会与用户进行交互,因此我们需要处理用户的点击事件.在ListView和GridView提供了onItemClickListener这个监听器,然而我们查找RecyclerView的API却没有类似的监听器,因此我们需要自己手动处理它的点击事件. 以下提供两种方法来实现处理Recycler