Win32消息循环机制等【转载】http://blog.csdn.net/u013777351/article/details/49522219

Dos的过程驱动与Windows的事件驱动

在讲本程序的消息循环以前,我想先谈一下Dos与Windows驱动机制的区别:php

DOS程序主要使用顺序的,过程驱动的程序设计方法。顺序的,过程驱动的程序有一个明显的开始,明显的过程及一个明显的结束,所以程序能直接控制程序事件或过程的顺序。虽然在顺序的过程驱动的程序中也有不少处理异常的方法,但这样的异常处理也仍然是顺序的,过程驱动的结构。css

而Windows的驱动方式是事件驱动,就是不禁事件的顺序来控制,而是由事件的发生来控制,全部的事件是无序的,所为一个程序员,在你编写程序时,你并不知道用户先按哪一个按纽,也不知道程序先触发哪一个消息。你的任务就是对正在开发的应用程序要发出或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。html

Windows编程的特色:

C语言编程至少有一个主程序,其名字是main()。Windows程序则至少两个主程序,一个是WinMain(),程序员

int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window );

另外一个是窗口过程函数WindowProc,它的函数原型为:编程

LRESULT CALLBACK WindowProc(
  HWND hwnd,      // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );

Windows应用程序的编程就围绕这两个部份进行的。其中WinMain函数为应用程序的入口点,它的名字必定要是WinMain。windows

在Windows中,应用程序经过要求Windows完成指定操做,而承担这项通讯任务的API函数就是Windows的相应窗口函数WindowProc。在dos里,程序能直接控制事件的发生顺序,结果等。而在Windows里,应用程序不直接调用任何窗口函数,而是等待Windows调用窗口函数,请求完成任务或返回信息。为保证Windows调用这个窗口函数,这个函数必须先向Windows登记,而后在Windows实施相应操做时回调,因此窗口函数又称为回调函数。WindowProc是一个主回调函数,Windows至少有一个回调函数。api

回调函数WindowProc在哪里定义的呢,请看这个语句:wc.lpfnWndProc = WindowProc ;将在第七讲里详谈.app

实例:在Windows中,能屡次同时运行同一个应用程序,即运行多个副本,每一个副本叫作一个“实例”。异步

如今让咱们把这个程序层层剥解开来,我把本身的理解慢慢地展现给你:ide

Win32编程步骤:

我把这个程序支解为四块:(一)创建,注册窗口类.(二)建立窗口.(三)显示和更新窗口.(四)建立消息循环.(五)终止应用程序.(六)窗口过程.(七)处理消息.

(一)注册窗口类:

(1)创建窗口类

WinMain()是程序的入口,它至关于一个中介人的角色,把应用程序(指小窗口)介绍给windows.首要的一步是登记应用程序的窗口类.

窗口种类是定义窗口属性的模板,这些属性包括窗口式样,鼠标形状,菜单等等,窗口种类也指定处理该类中全部窗口消息的窗口函数.只有先创建窗口种类,才能根据窗口种类来建立Windows应用程序的一个或多个窗口.建立窗口时,还能够指定窗口独有的附加特性.窗口种类简称窗口类,窗口类不能重名.在创建窗口类后,必须向Windows登记.

创建窗口类就是用WNDCLASS结构定义一个结构变量,在这个程序中就是指 WNDCLASS wc ;而后用本身设计的窗口属性的信息填充结构变量wc的域.

要WinMain登记窗口类,首先要填写一个WNDCLASS结构,其定义以下所示:

typedef struct _WNDCLASSA {    UINT style ;         //窗口类风格    WNDPROC lpfnWndProc ;    //指向窗口过程函数的指针    int cbClsExtra ;       //窗口类附加数据    int cbWndExtra ;       //窗口附加数据    HINSTANCE hInstance ;    //拥有窗口类的实例句柄    HICON hIcon ;        //最小窗口图标    HCURSOR hCursor ;      //窗口内使用的光标    HBRUSH hbrBackground ;   //用来着色窗口背景的刷子    LPCSTR lpszMenuName ;    //指向菜单资源名的指针    LPCSTR lpszClassName ;   // 指向窗口类名的指针 }WNDCLASS; // 增强版 typedef struct _WNDCLASSEX { UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX; 

在VC6.0里面,把光标定位在WNDCLASS上,按F1,便可启动MSDN,在MSDN里你可看到这个结构原形.在下节讲解这些参数在本程序中的具体用法.

(2)注册窗口类

(1)第一个参数:成员style控制窗口的某些重要特性,在WINDOWS.H中定义了一些前缀为CS的常量,在程序中可组合使用这些常量.也可把sytle设为0.本程序中为wc.style = CS_HREDRAW | CS_VREDRAW,它表示当窗口的纵横坐标发生变化时要重画整个窗口。你看:不管你怎样拉动窗口的大小,那行字都会停留在窗口的正中部,而假如把这个参数设为0的话,当改动窗口的大小时,那行字则不必定处于中部了。

(2)第二个参数:lpfnWndProc包括一个指向该窗口类的消息处理函数的指针,此函数称为窗口过程函数。它将接收Windows发送给窗口的消息,并执行相应的任务。其原型为:

long FAR PASCAL WndProc(HWND ,unsigned,WORD,LONG);而且必须在模快定义中回调它。WndProc是一个回调函数(见第五节),若是暂时没法理解这个模糊的概念意味着什么,可先放过,等到讲消息循环时再详谈。

(3)第三,四个参数:cbWndExtra域指定用本窗口类创建的全部窗口结构分配的额外字节数。当有两个以上的窗口属于同一窗口类时,若是想将不一样的数据和每一个窗口分别相对应。则使用该域颇有用。这般来说,你只要把它们设为0就好了,没必要过多考虑。

(4)第五个参数:hInstance域标识应用程序的实例hInstance,固然,实例名是能够改变的。wc.hInstance = hInstance ;这一成员可以使Windows链接到正确的程序。

(5)第六个参数:成员hIcon被设置成应用程序所使用图标的句柄,图标是将应用程序最小化时出如今任务栏里的的图标,用以表示程序仍驻留在内存中。Windows提供了一些默认图标,咱们也可定义本身的图标,VC里面专有一个制做图标的工具。

(6)第七个参数: hCursor域定义该窗口产生的光标形状。LoadCursor可返回固有光标句柄或者应用程序定义的光标句柄。IDC_ARROW表示箭头光标.

(7)第八个参数:wc.hbrBackground域决定Windows用于着色窗口背景的刷子颜色,函数GetStockObject返回窗口的颜色,本程序中返回的是白色,你也能够把它改变为红色等其余颜色.试试看

(8)第九个参数:lpszMenuName用来指定菜单名,本程序中没有定义菜单,因此为NULL。

(9)第十个参数:lpszClassName指定了本窗口的类名。

当对WNDCLASS结构域一一赋值后,就可注册窗口类了,在建立窗口以前,是必需要注册窗口类的,注册窗口类用的API函数是RegisterClass,注册失败的话,就会出现一个对话框如程序所示,函数RegisterClass返回0值,也只能返回0值,由于注册不成功,程序已经不能再进行下去了。

在本程序中注册窗口类以下:

if (!RegisterClass (&wc)) {   MessageBox (NULL,   TEXT ("This program requires Windows NT!"),   szAppName,MB_IConERROR) ;   return 0 ; }

(二)建立窗口

注册窗口类后,就能够建立窗口了,本程序中建立窗口的有关语句以下:

HWND CreateWindow(
  LPCTSTR lpClassName,  // pointer to registered class name LPCTSTR lpWindowName, // pointer to window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // handle to menu or child-window identifier HANDLE hInstance, // handle to application instance LPVOID lpParam // pointer to window-creation data );

参数1:登记的窗口类名,这个类名刚才我们在注册窗口时已经定义过了。

参数2:用来代表窗口的标题。

参数3: 用来代表窗口的风格,若有无最大化,最小化按纽啊什么的。

参数4,5: 用来代表程序运行后窗口在屏幕中的坐标值。

参数6,7: 用来代表窗口初始化时(即程序初运行时)窗口的大小,即长度与宽度。

参数8: 在建立窗口时能够指定其父窗口,这里没有父窗口则参数值为0。

参数9: 用以指明窗口的菜单,菜单之后会讲,这里暂时为0。

最后一个参数是附加数据,通常都是0。

CreateWindow()的返回值是已经建立的窗口的句柄,应用程序使用这个句柄来引用该窗口。若是返回值为0,就应该终止该程序,由于可能某个地方出错了。若是一个程序建立了多个窗口,则每一个窗口都有各自不一样的句柄.

(三)显示和更新窗口

API函数CreateWindow建立完窗口后,要想把它显示出现,还必须调用另外一个API函数ShowWindows.形式为:
ShowWindow (hwnd, iCmdShow);

其第一个参数是窗口句柄,告诉ShowWindow()显示哪个窗口,而第二个参数则告诉它如何显示这个窗口:最小化(SW_MINIMIZE),普通(SW_SHOWNORMAL),仍是最大化(SW_SHOWMAXIMIZED)。WinMain在建立完窗口后就调用ShowWindow函数,并把iCmdShow参数传送给这个窗口。你可把iCmdShow改变为这些参数试试。

WinMain()调用完ShowWindow后,还须要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即便窗口获得更新.

(四)建立消息循环

主窗口显示出来了,WinMain就开始处理消息了,怎么作的呢?

Windows为每一个正在运行的应用程序都保持一个消息队列。当你按下鼠标或者键盘时,Windows并非把这个输入事件直接送给应用程序,而是将输入的事件先翻译成一个消息,而后把这个消息放入到这个应用程序的消息队列中去。应用程序又是怎么来接收这个消息呢?这就讲讲消息循环了。

应用程序的WinMain函数经过执行一段代码从她的队列中来检索Windows送往她的消息。而后WinMain就把这些消息分配给相应的窗口函数以便处理它们,这段代码是一段循环代码,故称为”消息循环”。这段循环代码是什么呢?好,往下看:

在我们的第二只小板凳中,这段代码就是:

……

MSG msg; //定义消息名 while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; //翻译消息 DispatchMessage (&msg) ; //撤去消息 } return msg.wParam ;

MSG结构在头文件中定义以下:

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

MSG数据成员意义以下:

参数1:hwnd是消息要发送到的那个窗口的句柄,这个窗口就是我们用CreateWindows函数建立的那一个。若是是在一个有多个窗口的应用程序中,用这个参数就可决定让哪一个窗口接收消息。

参数2:message是一个数字,它惟一标识了一种消息类型。每种消息类型都在Windows文件中定义了,这些常量都以WM_开始后面带一些描述了消息特性的名称。好比说当应用程序退出时,Windows就向应用程序发送一条WM_QUIT消息。

参数3:一个32位的消息参数,这个值的确切意义取决于消息自己。

参数4:同上。

参数5:消息放入消息队列中的时间,在这个域中写入的并非日期,而是从Windows启动后所测量的时间值。Windows用这个域来使用消息保持正确的顺序。

参数6:消息放入消息队列时的鼠标坐标.

消息循环以GetMessage调用开始,它从消息队列中取出一个消息:

GetMessage(&msg,NULL,0,0),第一个参数是要接收消息的MSG结构的地址,第二个参数表示窗口句柄,NULL则表示要获取该应用程序建立的全部窗口的消息;第三,四参数指定消息范围。后面三个参数被设置为默认值,这就是说你打算接收发送到属于这个应用程序的任何一个窗口的全部消息。在接收到除WM_QUIT以外的任何一个消息后,GetMessage()都返回TRUE。若是GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其余消息,则返回TRUE。所以,在接收到WM_QUIT以前,带有GetMessage()的消息循环能够一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。 均为NULL时就表示获取全部消息。

消息用GetMessage读入后(注意这个消息可不是WM_QUIT消息),它首先要通过函数TranslateMessage()进行翻译,这个函数会转换成一些键盘消息,它检索匹配的WM_KEYDOWN和WM_KEYUP消息,并为窗口产生相应的ASCII字符消息(WM_CHAR),它包含指定键的ANSI字符.但对大多数消息来讲它并不起什么做用,因此如今没有必要考虑它。

下一个函数调用DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。咱们在讲到登记窗口类时曾提到过,登记窗口类时,咱们曾指定Windows把函数WindosProc做为我们这个窗口的窗口过程(就是指处理这个消息的东东)。就是说,Windows会调用函数WindowsProc()来处理这个消息。在WindowProc()处理完消息后,代码又循环到开始去接收另外一个消息,这样就完成了一个消息循环。

下一个出场的东东就是窗口过程了,先歇一下子再说吧??

(五)终止应用程序:

Windows是一种非剥夺式多任务操做系统。只有的应用程序交出CPU控制权后,Windows才能把控制权交给其余应用程序。当GetMessage函数找不到等待应用程序处理的消息时,自动交出控制权,Windows把CPU的控制权交给其余等待控制权的应用程序。因为每一个应用程序都有一个消息循环,这种隐式交出控制权的方式保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条语句。

当WinMain函数把控制返回到Windows时,应用程序就终止了。应用程序的启动消息循环前要检查引导出消息循环的每一步,以确保每一个窗口已注册,每一个窗口都已建立。如存在一个错误,应用程序应返回控制权,并显示一条消息。

可是,一旦WinMain函数进入消息循环,终止应用程序的惟一办法就是使用PostQuitMessage把消息WM_QUIT发送到应用程序队列。当GetMessage函数检索到WM_QUIT消息,它就返回NULL,并退出消息外循环。一般,当主窗口正在删除时(即窗口已接收到一条WM_DESTROY消息),应用程序主窗口的窗口函数就发送一条WM_QUIT消息。

虽然WinMain指定了返回值的数据类型,但Windows并不使用返回值。不过,在调试一应用程序时,返回值地有用的。一般,可以使用与标准C程序相同的返回值约定:0表示成功,非0表示出错。PostQuitMessage函数容许窗口函数指定返回值,这个值复制到WM_QUIT消息的wParam参数中。为了的结束消息循环以后返回这个值,咱们的第二只小板凳中使用了如下语句:

return msg.wParam ; //表示从PostQuitMessage返回的值

例如:当Windows自身终止时,它会撤消每一个窗口,但不把控制返回给应用程序的消息循环,这意味着消息循环将永远不会检索到WM_QUIT消息,而且的循环以后的语句也不能再执行。Windows的终止前的确发送一消息给每一个应用程序,于是标准C程序一般会的结束前清理现场并释放资源,但Windows应用程序必须随每一个窗口的撤消而被清除,不然会丢失一些数据。

(六)窗口过程,窗口过程函数

如前所述,函数GetMessage负责从应用程序的消息队列中取出消息,而函数DispatchMessage()要求Windows将消息传送给在MSG结构中为窗口所指定的窗口过程。而后出台的就是这个窗口过程了,这个窗口过程的任务是干什么呢?就是最终用来处理消息的,就是消息的处理器而已,那么这个函数就是WindowProc,在Visual C++6.0中按F1启动MSDN,按下面这个路径走下来:

PlatForm SDK–>User Interface services–>Windows user Interface–>Windowing–>Window Procedures–>Window Procedure Reference–>Windows Procedure Functions–>WindowProc

啊,太累了,不过咱们终于的MSDN中找到了这个函数,前几回我讲解这些API函数的时候,都是的知道的状况下搜索出来的,因此没有详细给出每一个函数的具体位置,而此次我倒是一点点去找的,还好,没被累死,体会到MSDN的庞大了吧,不过我用的是MSDN2000,是D版的,三张光盘装。你用的MSDN若是按这个路径走下去的话,可能会找不到,不过我想大体也是在这个位置了,找找看!!!

LRESULT CALLBACK WindowProc
(
    HWND hwnd, // handle to window UINT uMsg, // message identifier WPARAM wParam, // first message parameter LPARAM lParam // second message parameter );

这个函数咱们的第二只小板凳里被咱们称为WndProc.

下面讲解:

不知你注意到了没有,这个函数的参数与刚刚提到的GetMessage调用把返回的MSG结构的前四个成员相同。若是消息处理成功,WindowProc的返回值为0.

Windows的启动应用程序时,先调用WinMain函数,而后调用窗口过程,注意:在咱们的这个程序中,只有一个窗口过程,实际上,也许有不止一个的窗口过程。例如,每个不一样的窗口类都 有一个与之相对应的窗口过程。不管Windows什么时候想传递一个消息到一窗口,都将调用相应的窗口过程。当Windows从环境,或从另外一个应用程序,或从用户的应用程序中获得消息时,它将调用窗口过程并将信息传给此函数。总之,窗口过程函数处理全部传送到由此窗口类建立的窗口所获得的消息。而且窗口过程有义务处理Windows扔给它的任何消息。咱们在学习Windows程序设计的时候,最主要的就是学习这些消息是什么以及是什么意思,它们是怎么工做的。

令咱们不解的是,在程序中咱们看不出来是哪个函数在调用窗口过程。它实际上是一个回调函数.前面已经提到,Windows把发生的输入事件转换成输入消息放到消息队列中,而消息循环将它们发送到相应的窗口过程函数,真正的处理是在窗口过程函数中执行的,在Windows中就使用了回调函数来进行这种通讯。

回调函数是输出函数中特殊的一种,它是指那些在Windows环境下直接调用的函数。一个应用程序至少有一个回调函数,由于在应用程序处理消息时,Windows调用回调函数。这种回调函数就是咱们前面提到的窗口过程,它对对应于一个活动的窗口,回调函数必须向Windows注册,Windows实施相应操做即行回调。

每一个窗口必须有一个窗口过程与之对应,且Windows直接调用本函数,所以,窗口函数必须采用FAR PASCAL调用约定。在咱们的第二只小板凳中,咱们的窗口函数为WndProc,必须注意这里的函数名必须是前面注册的窗口类时,向域wc.lpfnWndProc所赋的WndProc。函数WndProc就是前面定义的窗口类所生成的全部窗口的窗口函数。

在咱们的这个窗口函数中,WndProc处理了共有两条消息:WM_PAINT和WM_DESTROY.

窗口函数从Windows中接收消息,这些消息或者是由WinMain函数发送的输入消息,或者是直接来自Windows的窗口管理消息。窗口过程检查一条消息,而后根据这些消息执行特定的动做未被处理的消息经过DefWindowProc函数传回给Windows做缺海上处理。

能够发送窗口函数的消息约有220种,全部窗口消息都以WM_开头,这些消息在头文件中被定义为常量。引发Windows调用窗口函数的缘由有不少,,如改变窗口大小啊,改变窗口在屏幕上的位置啊什么的。

Windows已经把任务扔给窗口过程了,窗口过程是怎么处理消息的呢?稍息一下,让咱们进行下一节:处理消息……

注:可能你看这些东西的时候有些乱,不过不要紧,这很正常,多看几下MSDN就慢慢明白了,有我写这个专题的时候,不少概念也太不清楚,不过等我查资料写下来后,感受渐渐有些东西也有了点眉目,由于这自己也是个进步的过程。 —小朱 
(七)处理消息 
窗口过程处理消息一般以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句。大多数windows proc都有具备下面形式的内部结构:

switch(uMsgId) { case WM_(something): //这里此消息的处理过程 return 0; case WM_(something else): //这里是此消息的处理过程 ruturn 0; default: //其余消息由这个默认处理函数来处理 return DefWindowProc(hwnd,uMsgId,wParam,lParam); }

在处理完消息后,要返回0,这很重要—–它会告诉Windows没必要再重试了。对于那些在程序中不许备处理的消息,窗口过程会把它们都扔给DefWindowProc进行缺省处理,并且还要返回那个函数的返回值。在消息传递层次中,能够认为DefWindowProc函数是最顶层的函数。这个函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各类通用操做,例如,画窗口的非用户区,更新窗口的正文标题等等等等。

再提示一下,以WM_的消息在Windows头文件中都被定义成了常量,如WM_QUIT=XXXXXXXXXXX,但咱们没有必要记住这个数值,也不可能记得住,咱们只要知道WM_QUIT就OK了。

在第二只小板凳中咱们只让窗口过程处理了两个消息:一个是WM_PAINT,另外一个是WM_DESTROY,先说说第一个消息—WM_PAINT.

关于WM_PAINT:

不管什么时候Windows要求重画当前窗口时,都会发该消息。也能够这样说:不管什么时候窗口非法,都必须进行重画。 哎呀,什么又是”非法窗口”?什么又是重画啊?你这人有没有完,嗯?

稍安勿燥,我比你还烦呢?我午餐到如今还没吃呢!你有点耐心,来点专业精神好很差???我开始在MSDN里面找有关这个方面的内容了,别急,我找找看:

Platform SDK–>Graphics and Multimedia Services–>Windows GDI–>Painting and Drawing–>Using the WM_PAINT Message—–终于找到了。

下面是一大套理论:

让咱们把Windows的屏幕想像成一个桌面,把一个窗口想像成一张纸。当咱们把一张纸放到桌面上时,它会盖住其余的纸,这样被盖住的其余纸上的内容都看不到了。但咱们只要把这张纸移开,被盖住的其余纸上的内容就会显示出来了—这是一个很简单的道理,谁都明白。

对于咱们的屏幕来讲,当一个窗口被另外一窗口盖住时,被盖住的窗口的某些部分就看不到了,咱们要想看到被盖住的窗口的所有面貌,就要把另外一个窗口移开,可是当咱们移开后,事情却起了变化—–极可能这个被盖住的窗口上的信息被擦除了或是丢失了。当窗口中的数据丢失或过时时,窗口就变成非法的了—或者称为”无效”。因而咱们的任务就来了,咱们必须考虑怎样在窗口的信息丢失时”重画窗口”–使窗口恢复成之前的那个样子。这也就是咱们在这第二只小板凳中调用UpdateWindow的缘由。

你忘记了吗?刚才咱们在(三)显示和更新窗口中有下面的一些文字:

WinMain()调用完ShowWindow后,还须要调用函数UpdateWindow,最终把窗口显示了出来。调用函数UpdateWindow将产生一个WM_PAINT消息,这个消息将使窗口重画,即便窗口获得更新.—这是程序第一次调用了这条消息。

为从新显示非法区域,Windows就发送WM_PAINT消息实现。要求Windows发送WM_PAINT的状况有改变窗口大小,对话框关闭,使用了UpdateWindows和ScrollWindow函数等。这里注意,Windows并不是是消息WM_PAINT的惟一来源,使用InvalidateRect或InvalidateRgn函数也能够产生绘图窗口的WM_PAINT消息……

一般状况下用BeginPaint()来响应WM_PAINT消息。若是要在没有WM_PAINT的状况下重画窗口,必须使用GetDC函数获得显示缓冲区的句柄。这里面再也不扩展。详细见MDSN。

这个BeginPaint函数会执行准备绘画所需的全部步骤,包括返回你用于输入的句柄。结束则是以EndPaint();

在调用完BeginPaint以后,WndProc接着调用GetClientRect:

GetClientRect(hwnd,&rect);

第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT类型的结构。查MSDN,可看到这个结构有四个成员。

WndProc作了一件事,他把这个RECT结构的指针传送给了DrawText的第四个参数。函数DrawText的目的就是在窗口上显示一行字—-“你好,欢迎你来到VC之路!”,有关这个函数的具体用法这里也不必说了吧。

关于WM_DESTROY

这个消息要比WM_PAINT消息容易处理得多:只要用户关闭窗口,就会发送WM_DESTROY消息(在窗口从屏幕上移去后)。

程序经过调用PostQuitMessage以标准方式响应WM_DESTROY消息:

PostQuitMessage (0) ;

这个函数在程序的消息队列中插入一个WM_QUIT消息。在(四)建立消息循环中咱们曾有这么一段话:

消息循环以GetMessage调用开始,它从消息队列中取出一个消息:

在接收到除WM_QUIT以外的任何一个消息后,GetMessage()都返回TRUE。若是GetMessage收到一个WM_QUIT消息,则返回FALSE,如收到其余消息,则返回TRUE。所以,在接收到WM_QUIT以前,带有GetMessage()的消息循环能够一直循环下去。只有当收到的消息是WM_QUIT时,GetMessage才返回FALSE,结束消息循环,从而终止应用程序。

Win32 API主消息循环的两种处理方法

主要介绍了Win32 API主消息循环的两种处理方法:使用GetMessage方法构造主消息循环、使用PeekMessage方法构造主消息循环。

使用GetMessage方法构造主消息循环

通常应用程序都使用用GetMessage方法构造主消息循环,该方法是得到一条线程 的消息。对于VS2005自动生成的Win32 Windows程序上面有些不足。 
由于VS2005生成的主消息循环以下;

// Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

简单看看的确没有问题,可是当咱们去查阅MSDN文档看到GetMessage消息时候能够看到这样一段

If there is an error, the return value is -1.

因此咱们应该把上面这个主循环修改成下面这样的形式,增长一个临死变量。

// Main message loop: BOOL bRet;//临时变量,存储GetMessage方法返回值 // Main message loop: while ((bRet = GetMessage(&msg, NULL, 0, 0))!=0) { if(bRet==-1) { //表示GetMessage得到的信息有错误 } else { TranslateMessage(&msg); DispatchMessage(&msg); } }

(2)使用PeekMessage方法构造主消息循环 
PeekMessage经常用于Windows开发游戏中,PeekMessage在处理得到消息时候和GetMessage同样,关键不一样的是PeekMessage在没有消息处理的时候还会继续保持循环激活状态,而且继续占用资源。

// Main message loop: while (true) { if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { if(msg.message == WM_QUIT) { break; } else //表示GetMessage得到的信息有错误 { TranslateMessage(&msg); DispatchMessage(&msg); } } else { //循环处理的函数 } }

Win32消息循环是一个死循环吗?若是是消息循环为何不会耗尽CPU?

1:Win32是个多任务抢占式操做系统,每运行一个程序(可执行文件),操做系统就建立一个进程和主线程,把程序的代码和数据映射到该进程地址空间,并为每一个线程分配了一个时间片,一个线程放弃CPU的处理权有、能够是时间片完了,I/O请求,还有就是程序本身要求放弃处理权,而GetMessage函数是一个阻塞函数,也就是你调用他就至关于主动放弃了CPU,引发线程上下文切换,从而其余线程能够获得CPU,但该函数会在有消息的时间激活而继续执行。若是你是获取消息用PeekMessage函数,那么你打开任务管理器,才知道什么叫作真正的浪费资源;

2:

while(1) { id=getMessage(...); if(id == quit) break; translateMessage(...); }

  当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。 
  在16位的系统中系统中只有一个消息队列,因此系统必须等待当前任务处理消息后才能够发送下一消息到相应程序,若是一个程序陷如死循环或是耗时操做时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统。 
  而32位的系统中每一运行的程序都会有一个消息队列,因此系统能够在多个消息队列中转换而没必要等待当前程序完成消息处理就能够获得控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/Windows98/NT就是这种系统。

3:曾有这样的疑问,为何不少资料中都有关于windows中的While(getmessage(&msg,Null,0,0)){..}消息循环不占用CPU的说法?今天特有关此事查了一下资料,原来是这样子啊! 
说,其实这里的while(){}循环是占用cpu的,只是getmessage()是一个阻塞型的函数,当消息队列中没有消息时,它会检查确认,当确认消息队列为空时,则进行V操做,从而使线程外于阻塞状态,不被激发,另外咱们知道外于sleep状态的线程是不占cpu的,是故当getmessage无返回值时,while()也不执行。整个线程被阻塞,从而不占用CPU资源。 
当Winows程序启动时,会注册一个窗口类,注册的窗口类中包括当前窗口的风格、消息处理函数等等。而后,程序建立一个该注册窗口类的主窗口,接着,显示这个主窗口并进入到消息循环。在消息循环中,将不断地从窗口自身的消息队列中读取消息,并调用注册的窗口消息处理函数对不一样的消息进行处理。

关于Windows中的系统消息循环占用CPU的疑问

GetMessage函数是一个阻塞型的函数,当消息队列中没有消息时,GetMessage会处于阻塞状态。一旦有消息到达,进程会被唤醒,GetMessage立刻返回。实现时,使用了一个信号量, GetMessage函数在肯定没有消息可读时,对这个信号量进行一个V操做,从而使线程阻塞。而PostMessage、SendNotifyMessage、SendSyncMessage等任何一个发送消息函数在发送完消息以后,都会读取这个信号量的值,当发现这个值等于零时,即表示读消息的线程当前已阻塞,这时就会做一次P操做,来唤醒睡眠的线程。

GetMessage与PeekMessage的区别

PeekMessage 返回 TRUE 的条件是有消息,若是没有消息返回 FALSE 
GetMessage 返回 TRUE 的条件是有消息且该消息不为 WM_QUIT 
   返回 FALSE 的条件是有消息且该消息 为 WM_QUIT

GetMessage不将控制传回给程序,直到从程序的消息队列中取得消息,可是PeekMessage老是马上传回,而不论一个消息是否出现。当消息队列中有一个消息时,PeekMessage的传回值为TRUE(非0),而且将按一般方式处理消息。当队列中没有消息时,PeekMessage传回FALSE(0)。 
这使得咱们能够改写普通的消息循环。咱们能够将以下所示的循环:

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

 

替换为下面的循环:

while (TRUE) { if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break ; TranslateMessage (&msg) ; DispatchMessage (&msg) ; } else { // 完成某些工做的其它行程序 } } return msg.wParam ;

注意,WM_QUIT消息被另外挑出来检查。在普通的消息循环中您没必要这么做,由于若是GetMessage接收到一个WM_QUIT消息,它将传回0,可是PeekMessage用它的传回值来指示是否获得一个消息,因此须要对WM_QUIT进行检查。 
若是PeekMessage的传回值为TRUE,则消息按一般方式进行处理。若是传回值为FALSE,则在将控制传回给Windows以前,还能够做一点工做(如显示另外一个随机矩形)。 
(尽管Windows文件上说,您不能用PeekMessage从消息队列中删除WM_PAINT消息,可是这并非什么大不了的问题。毕竟,GetMessage并不从消息队列中删除WM_PAINT消息。从队列中删除WM_PAINT消息的惟一方法是令窗口显示区域的失效区域变得有效,这能够用ValidateRectValidateRgn或者BeginPaintEndPaint对来完成。若是您在使用PeekMessage从队列中取出WM_PAINT消息后,同日常同样处理它,那么就不会有问题了。所不能做的是使用以下所示的程序代码来清除消息队列中的全部消息:

while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) ;

 

这行叙述从消息队列中删除WM_PAINT以外的全部消息。若是队列中有一个WM_PAINT消息,程序就会永远地陷在while循环中。

PeekMessage和GetMessage函数的主要区别有:

  1. GetMessage的主要功能是从消息队列中“取出”消息,消息被取出之后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,若是有消息,就返回true,不然返回false。也可使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),若是设置为PM_REMOVE,消息则被取出并从消息队列中删除;若是设置为PM_NOREMOVE,消息就不会从消息队列中取出。
  2. 若是GetMessage从消息队列中取不到消息,则线程就会被操做系统挂起,等到OS从新调度该线程时,二者的性质不一样:使用GetMessage线程仍会被挂起,使用PeekMessage线程会获得CPU的控制权,运行一段时间。
  3. GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就当即返回,从返回值判断是否取到了消息。

咱们也能够说,PeekMessage是一个具备线程异步行为的函数,无论消息队列中是否有消息,函数都会当即返回。而GetMessage则是一个具备线程同步行为的函数,若是消息队列中没有消息的话,函数就会一直等待,直到消息队列中至少有一条消息时才返回。

若是消息队列中没有消息,PeekMessage老是能返回,这就至关于在执行一个循环,若是消息队列一直为空, 它就进入了一个死循环。GetMessage则不可能由于消息队列为空而进入死循环。

在Windows的内部,两个函数执行着相同的代码。 
具体状况具体分析,没法说明到底哪个更好一些,这要根据实际的应用状况而定。

SendMessage、PostMessage原理

本文讲解SendMessage、PostMessage两个函数的实现原理,分为三个步骤进行讲解,分别适合初级、中级、高级程序员进行理解,三个步骤分别为:

一、SendMessage、PostMessage的运行机制。

二、SendMessage、PostMessage的运行内幕。

三、SendMessage、PostMessage的内部实现。

注:理解这篇文章以前,必须先了解Windows的消息循环机制。

SendMessage、PostMessage原理

一、SendMessage、PostMessage的运行机制

咱们先来看最简单的。

SendMessage能够理解为,SendMessage函数发送消息,等待消息处理完成后,SendMessage才返回。稍微深刻一点,是等待窗口处理函数返回后,SendMessage就返回了。

PostMessage能够理解为,PostMessage函数发送消息,不等待消息处理完成,马上返回。稍微深刻一点,PostMessage只管发送消息,消息有没有被送到则并不关心,只要发送了消息,便马上返回。

对于写通常Windows程序的程序员来讲,可以这样理解也就足够了。但SendMessage、PostMessage真的是一个发送消息等待、一个发送消息不等待吗?具体细节,下面第2点将会讲到。

二、SendMessage、PostMessage的运行内幕

在写通常Windows程序时,如上第1点讲到的足以应付,其实咱们能够看看MSDN来肯定SendMessage、PostMessage的运行内幕。

在MSDN中,SendMessage解释如为:The SendMessage function sends the specified message to a window or windows. It calls the window procedure for the specified window and does not return until the window procedure has processed the message.

翻译成中文为:SendMessage函数将指定的消息发到窗口。它调用特定窗口的窗口处理函数,而且不会当即返回,直到窗口处理函数处理了这个消息。

再看看PostMessage的解释:The PostMessage function places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for the thread to process the message.

翻译成中文为:PostMessage函数将一个消息放入与建立这个窗口的消息队列相关的线程中,并马上返回不等待线程处理消息。

仔细看完MSDN解释,咱们了解到,SendMessage的确是发送消息,而后等待处理完成返回,但发送消息的方法为直接调用消息处理函数(即WndProc函数),按照函数调用规则,确定会等消息处理函数返回以后,SendMessage才返回。而PostMessage却没有发送消息,PostMessage是将消息放入消息队列中,而后马上返回,至于消息什么时候被处理,PostMessage彻底不知道,此时只有消息循环知道被PostMessage的消息什么时候被处理了。

至此咱们拨开了一层疑云,原来SendMessage只是调用咱们的消息处理函数,PostMessage只是将消息放到消息队列中。


[1]关于如何设置,让VS2005载入Symbol,能够查看我写的另一篇文章:“让Visual Studio载入Symbol(pdb)文件”,地址:http://blog.csdn.net/xt_xiaotian/archive/2010/03/16/5384111.aspx 
原创博客: 
http://blog.chinaunix.net/uid-20496675-id-1664090.html 
http://blog.itpub.net/7668308/viewspace-853757/ 
http://zhidao.baidu.com/link?url=fQT7pgXRKoFnpQlzYRjhdqJtPe8E1Lp1G8t1M0Sg8lm-mfNC5zF2D83FFxwWtJA5UL_E81lHT9uxuITlMIlLg_ 
http://blog.sina.com.cn/s/blog_4ba53587010007lb.html 
http://www.cnblogs.com/scope/archive/2009/06/14/1503088.html 
http://blog.csdn.net/gencheng/article/details/9376881 
http://blog.csdn.net/xt_xiaotian/article/details/5384137

相关文章
相关标签/搜索