热度 19
2012-7-6 16:05
9269 次阅读|
0 个评论
VC编程Windows消息处理机制、阻塞试验、SetTimer、MessageBox、小心消息响应处理函数多个并存 wxleasyland@sina.com 2012.7 wxleasyland试验: VC6标准WIN32程序,Windows消息处理机制: 1.在注册窗口类时,指定了消息处理函数WndProc()。 2.WinMain()里有消息循环: while (GetMessage(msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); DispatchMessage(msg); } } 3.在WndProc()里,对各个消息进行处理!! 消息队列是线程所有,并非窗口所有和进程所有。 一个线程的所有窗口,比如父窗口(主窗口)、对话框、消息框等都是使用这个线程的同一个消息队列!!!! 窗口有消息循环。窗口里的控件应该就没有消息循环了吧。 试验: 一、MFC中,用TIMER,发现,在TIMER响应函数中,用MessageBox(),则会不断地出现MessageBox。 而且当前响应函数会停止在MessageBox上,直到MessageBox返回才继续执行后面的语句! 用SendMessage或PostMessage来试消息响应,也是一样:响应函数中有MessageBox(),则会出现多个MessageBox。 说明MessageBox阻塞了函数,却没有阻塞消息分发。 二、VC建WIN32程序(标准HELLO WORLD),试验发现,如果WndProc()里有MessageBox,当前的WndProc()会暂时停止在MessageBox,直到MessageBox返回才继续执行后面的语句!! 如果MessageBox出现,这时一个WM_PAINT出现了,则会执行一个新的WndProc()。即WndProc()并行出现了!!线程只有一个,但WndProc()可以出现多个! 用ABOUT对话框代替MessageBox,情况也是一样!ABOUT对话框出现时,父窗口(即主程序)点不出来,但父窗口WM_PAINT能正常运行!!! 即像别的文章说的, 对话框和消息框会有自己的消息循环,但线程是同一个线程,所以线程的所有消息会被分发处理。 三、再试下面程序, void CUnicodewcharDlg::OnButton4() { SendMessage(UM_SOCK); // UM_SOCK为自定义消息,已经和OnRecv相关联 } BOOL running=FALSE; void CUnicodewcharDlg::OnRecv() { if (running==TRUE) {::MessageBox(NULL,"ERROR","",MB_OK); return;} running=TRUE; ::MessageBox(NULL,"OK","",MB_OK); ::MessageBox(NULL,"返回","",MB_OK); running=FALSE; } 发现,按OnButton4后,先出现一个"OK"消息框,再点OnButton4,就出现"ERROR"消息框。 如果"OK"消息框关闭了,则不会出现"返回"消息框,再点OnButton4,仍是出现"ERROR"消息框!!!!!!! 一定要"ERROR"消息框和"OK"消息框都关闭了,才会出现"返回"消息框,再点OnButton4,才会出现"OK"消息框!! 说明是: 父窗消息循环,调用消息响应函数生成了第一个消息框,同时父窗调用的消息响应函数阻塞,父窗消息循环阻塞;第一个消息框开始接手负责消息循环(是自己有消息循环还是把父窗的消息循环重入了继续使用?)。 第一个消息框消息循环,调用消息响应函数生成了第二个消息框,同时第一个消息框调用的消息响应函数阻塞,第一个消息框消息循环阻塞;第二个消息框开始接手负责消息循环(是自己有消息循环还是把父窗的消息循环重入了继续使用?)。 第二个消息框消息循环。。。 即:父窗阻塞-第一个消息框阻塞-第二个消息框 如果第一个消息框关闭,但第二个消息框未关闭,则第一个消息框调用的消息响应函数仍在阻塞中,故父窗调用的消息响应函数也在阻塞中,所以继续运行不下去,而第二个消息框仍在进行消息循环。只有第二个消息框关闭了,第一个消息框调用的消息响应函数才会继续运行并返回,然后父窗调用的消息响应函数才会继续运行并返回!!! 第二个消息框消息循环,收到父窗WM_PAINT,调用父窗消息响应函数,WM_PAINT这一部分如果没有阻塞,则消息响应函数可以马上返回。再进行下一个消息处理。所以父窗不会因为没有刷新而失去显示,否则就显示不正常了。 消息响应函数可以同时多次进入 ,比如父窗调用了阻塞了,第一个消息框又调用了阻塞了,第二个消息框又调用了。。。 三、试验子窗口是自己有消息循环还是把父窗的消息循环重入了继续使用? 用WIN32(标准HELLO WORLD)程序来试: 在WinMain()中: while (GetMessage(msg, NULL, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); if(msg.message ==WM_COMMAND) WinExec("cmd /k echo TranslateMessage()ok",SW_SHOW); //加上这句 DispatchMessage(msg); if(msg.message ==WM_COMMAND) WinExec("cmd /k echo DispatchMessage()ok",SW_SHOW); //加上这句 } } 在WndProc()中: case IDM_ABOUT: //DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About); //这句屏蔽掉 MessageBox(NULL,"in about","",MB_OK); //加上这句,这样父窗就可以点出来了 发现,点菜单的ABOUT..., WinExec(显示TranslateMessage()ok)执行,显示出MessageBox;MessageBox关闭后,WinExec(显示DispatchMessage()ok)才会执行。 OK,符合正常的消息处理原则,即DispatchMessage()要等到WndProc()执行完才会返回。 重新开始试: 1. 点菜单的ABOUT..., WinExec(显示TranslateMessage()ok)执行,显示出第一个MessageBox。 2. 再到主窗,点菜单的ABOUT..., 显示出第二个MessageBox,但WinExec(显示TranslateMessage()ok)没有执行!! 3. 关闭第一个MessageBox,WinExec(显示DispatchMessage()ok)仍没有执行。 4. 最后,关闭第二个MessageBox,DispatchMessage()ok)才执行。 结论: 对话框、消息框等子窗口是用自已默认的消息循环,而不是重入到父窗的消息循环!! 主窗阻塞了,第1个子窗负责消息循环,调用线程的消息响应函数(包括主窗、第1个子窗的消息响应函数); 第1个子窗阻塞了,第2个子窗负责消息循环,调用线程的消息响应函数(包括主窗、第1个子窗、第2个字窗的消息响应函数);。。。 子窗是一级一级阻塞下去的,后一个子窗关闭了,才能使前一个子窗的消息响应函数解除阻塞。 如果处在中间的子窗关闭了,它的消息响应函数仍还在阻塞中!只有它后面的子窗都关闭了,它的消息响应函数才会返回。 所以,如果是基于消息来处理事件,比如,网络SOCKET异步编程,消息处理函数中,有 用到消息框MessageBox或对话框,则要小心 ,以免多个事件消息同时发生。因为消息框出现后,当前消息处理函数阻塞了,但消息循环仍在进行,如果有新的消息事件产生,则会运行新的消息处理函数,则有可能会产生不希望的后果。 四、后来,试验网络SOCKET异步编程,确实,弹出MessageBox后,消息循环仍在继续,如果有收到FD_READ、FD_ACCEPT等,都会执行消息处理函数。 如果消息处理函数中弹出MessageBox,这时下一个消息来了,则会再运行一个新的消息处理函数!而不是阻塞。即 消息处理函数同时被多次调用,多个并存! =================================== 下面引用网络的文章: =================================== http://hi.baidu.com/suninf/blog/item/c3b7c738fa751cf4b311c74d.html DispatchMessage 2008年10月18日 星期六 14:33 windows消息处理机制是这样的: 首先系统(也就是windows)把来自硬件(鼠标,键盘等消息)和来自应用程序的消息 放到一个系统消息队列中去。 而应用程序需要有自己的消息队列,也就是线程消息队列, 每一个线程有自己的消息队列 ,对于多线程的应用程序就有和线程数目相等的线程消息队列。 winsows消息队列把得到的消息发送到线程消息队列,线程消息队列每次取出一条消息发送到指定窗口,不断循环直到程序退出。这个循环就是靠消息环(while(GetMessage()) TranslateMessage();DispatchMessage(); 实现的。 GetMessage()只是从线程消息中取出一条消息,而DispatchMessage 则把取出的消息发送到目的窗口。如果收到WM_CLOSE消息则结束循环,发送postqiutmessage(0),处理WM_DESTROY销毁窗口! 其实问题的关键在于:DispatchMessage到底干了什么? 如果只是去调用相应的窗口,那自己写个switch不就可以了 ? DispatchMessage与switch不同之处在于DispatchMessage会 先调用windows,进入管态(大概是range 0),然后再由windows调用 窗口的函数。 为什么这么麻烦? 因为这样windows就可以知道你的程序运行到什么情况了, windows来调用你的窗口,这样你的窗口返回的时候,windows就知道你已经处理过一个消息了,如果没有新的消息进入消息队列,windows就不再会给你的进程分配时间片。 如果是你自己写switch的话,windows就不可能这样灵活的分配时间,资源利用率就会降低。 那么还要消息循环干什么,windows直接把消息发给窗口不就可以了吗? 因为你要在消息循环里把KEY_DOWN和KEY_UP组合成WM_CHAR,还可以直接屏蔽掉许多对你来说无用的消息,加快速度。 GetMessage: 从线程的消息队列取出一个消息 。GetMessage是从系统为每个应用程序自动分配的消息对列的头部得到一个消息。 TranslateMessage: 将msg结构传给Windows,进行一些转换,比如A键按下,转换成WM_CHAR消息等 。TranslateMessage是对一些键盘事件做预处理。TranslateMessage是翻译需要翻译的消息。 翻译消息不是简单的转换,一个消息被翻译后,可能会产生几个消息。 DispatchMessage(): 再将msg结构传给Windows,Windows将该消息发给窗口过程,由窗口过程处理。 DispatchMessage()则会把翻译好的消息发送到系统的消息处理函数中,而这个函数又会把这个消息传递到注册窗体时用户指定的消息处理函数中 前面已经介绍从系统队列里获取一条消息,然后经过快捷键的函数检查,又通过字符消息函数的转换,最后要做的事情就是调用DispatchMessage函数,它的意思就是说要把这条消息发送到窗口里的消息处理函数WindowProc。 =================================== 消息的循环过程大致为(关于消息的具体情况不再说明) 1. 消息循环调用GetMessage()从消息队列中查找消息进行处理,如果消息队列为空,程序将停止执行并等待(程序阻塞)。 2. 事件发生时导致一个消息加入到消息队列(例如系统注册了一个鼠标点击事件),GetMessage()将返回一个正值,这表明有消息需要被处理,并且消息已经填充到传入的MSG参数中;当传入WM_QUIT消息时返回0;如果返回值为负表明发生了错误。 3. 取出消息(在Msg变量中)并将其传递给TranslateMessage()函数,这个函数做一些额外的处理:将虚拟键值信息转换为字符信息。这一步实际上是可选的,但有些地方需要用到这一步。 4. 上面的步骤执行完后,将消息传递给DispatchMessage()函数。DispatchMessage()函数将消息分发到消息的目标窗口,并且查找目标窗口过程函数,给窗口过程函数传递窗口句柄、消息、wParam、lParam等参数然后调用该函数。 5. 在窗口过程函数中,检查消息和其他参数,你可以用它来实现你想要的操作。如果不想处理某些特殊的消息,你应该总是调用DefWindowProc()函数,系统将按按默认的方式处理这些消息(通常认为是不做任何操作)。 6. 一旦一个消息处理完成,窗口过程函数返回,DispatchMessage()函数返回,继续循环处理下一个消息。 ================================ DispatchMessage将消息分发到窗口函数中,请问:DispatchMessage是直接返回还是等待WndProc处理完毕再返回? 用::DiapatchMessage派送消息,在窗口处理过程(WinProc,窗口函数)返回之前,他是阻塞的,不会立即返回,也就是消息循环此时不能再从消息队列中读取消息,直到::DispatchMessage返回。如果你在窗口函数中执行一个死循环操作,就算你用 PostQuitMessage函数退出,程序也会down掉。 while(1) { PostQuitMessage(0); //程序照样down. } 所以,当窗口函数处理没有返回的时候,消息循环是不会从消息队列中读取消息的。这也是为什么在 模式对话框 中要自己用无限循环来继续消息循环,因为这个无限循环阻塞了原来的消息循环,所以,在这个无限循环中要用GetMessage,PeekMessage,DispatchMessage来从消息队列中读取消息并派送消息了。要不然程序就不会响应了,这不是我们所希望的。 模式对话框是卡住了啊,只不过它内部死循环中又有PeekMessage,GetMessage什么的了,所以不会对界面操作产生影响 我记得IsDialogMessage只适用于模式对话框吧,非模式对话框不需要的。 还有,如果是模式对话框,下面的窗口还是可以处理WM_PAINT的,你移动上面的模式对话框,下面的窗口可以自己重画 刚才试了一下,DispatchMessage果然是会卡住等待WndProc返回才返回的。模式对话框的确是有内嵌消息循环的,包括MessagaBox也是一样。 WM_PAINT和WM_TIMER都是优先级不同的,WM_TIMER优先级最低,只有队列里没有其他消息才会执行,WM_PAINT则较高 从窗口创建开始说: CreateWindowEx,这个会向窗口例程直接投递WM_CREATE,而不进入消息队列。其他消息则是比较正常的经过消息队列,然后通过GetMessage和DispatchMessage取消息和分发到相应例程,直到取到WM_QUITE就结束消息循环。 对于按键和鼠标消息则是先进入系统消息队列,然后系统队列会分发到相应的线程消息队列。 记住,消息队列是线程所有,并非窗口所有和进程所有,当然,系统队列例外。 =================================================================== 刚才学了用 c 和 windows API 创建一个简单的windows 窗口程序,步骤如下: 1.设计一个窗口类 2.创建窗口 3.显示窗口 4.定义消息结构体,开始消息循环,如: while(GetMessage(msg,NULL,0,0)){ 略... } 5.编写窗口过程函数,如: LRESULT CALLBACK WinSunProc(参数略){ switch(uMsg) { case 某一事件; 执行某函数; break; case 某一事件; 执行某函数; break; default; 略 } } 我的问题是, 当发生了一个消息时, 会执行switch 语句 中的某条case语句, 就是在此时此刻,while语句还在监听消息吗?????? 如何你在窗口函数case不返回的话这个窗口就会没响应的,你可以试试看在一个case里写Sleep(5000) 有些人已经说对了,GetMessage只负责从消息队列里面取出一条消息,TranslateMessage将键盘敲键的消息转换成WM_CHAR消息,DispatchMessage就负责调用你的窗口函数,其实相当于 ... DispatchMessage(...) { .... WinSunProc(...): //事实上这里是通过你注册窗口类时候给Windows的函数指针来实现的,但是效果和直接调用一样。 } 现在整个流程就很清楚了,GetMessage - DispatchMessage - WinSunProc 然后再返回到主循环进行下一条消息的操作,如果你在WinSunProc里面一直不返回,那么程序是无法处理下条消息的。 处理消息的时候如果又有其他消息过来是没关系的,Windows的GetMessage是从消息“队列”里面去消息的,没来得处理的消息是会排队在消息队列里面的,微软说了Windows的消息队列足够长,一般不会出现消息丢失的情况,具体没说多长,可能根据操作系统版本不同有不同的长度限制。 另外GetMessage还有个特性,如果程序的消息队列是空的,也就是没有消息了,那么GetMessage就不会返回,直到等到下一条消息来再返回,Windows会将处于等待的程序转入Idle模式,所以那个while循环是不会出现CPU100%的占用率的。 如果你希望在程序没有消息的时候在后台做点什么事情,那么就可以利用 PeekMessage ,典型的 MFC就是利用了PeekMessage来运作消息循环的 ,PeekMessage在队列中有消息的时候则把消息取回, 没消息的时候也会立刻返回 ,这样你就可以在没消息的时候做点别的事情。 MFC的CWinApp类在Run这个函数中包含了消息循环,在没有消息的时候,Run会去调用 CWinApp::OnIdle ,默认的OnIdle会负责释放不需要再使用的动态连接库文件。 如果Run里面的PeekMessage取到消息,他则调用 CWinApp::PumpMessage 函数,PumpMessage就负责调用DispatchMessage把消息转交给窗口函数。 windows在处理消息时,会使用一个队列来存放所有消息,即消息队列。在执行case语句时,虽然窗口没有关心消息队列,但是如果此时又来了一个消息,windows会自动将该消息放入队列之中,保证消息不会丢失。当case语句处理完毕,窗口再一次运行到GetMessage时,会查看队列中有没有未处理的消息存在,通过这种方式获取之前发生但还没有处理的消息,因此不会出现对消息视而不见的情况 windows采用消息队列来处理消息,没侦听到一个事件,系统会将此消息加到消息队列,再按顺序或者按权重来处理消息。 所有创建了窗口的Windows程序,都需要运行一个消息循环 : while (GetMessage(msg, hWnd, 0, 0) 0) { TranslateMessage(msg); DispatchMessage(msg); } 这里的hWnd就是创建的窗口句柄,上述循环会不断的把该窗口(hWnd)相关的消息取出来,并分发到消息处理函数当中。 GetMessage函数不是用来监听的,是用来获取当前线程消息队列当中的消息的,其中的第二个参数如果传递一个窗口句柄,那么就会获取该窗口相关的消息,如果传NULL,那么会将线程消息队列中所有的消息都取出来。如果创建了多个窗口,而只对其中一个窗口句柄调用GetMessage形成消息循环,那么别的窗口都会毫无响应。 补充说明: 消息队列是操作系统为每个需要处理消息的线程创建的,任何线程只要调用过与消息有关的函数(如GetMessage,PeekMessage),操作系统就会为该线程创建消息队列 。 GetMessage在消息列队中没有消息时是阻塞的,也就是没有消息的情况下他会通知系统把时间片交给其他进程或线程 ==================================== 1 :消息处理中第一条消息是不是 WM_CREATE 2:当接收到WM_CREATE消息时,他的附加参数中有没有 窗口的实例句柄 在那个参数中或者 有没有其他办法在消息处理中获得窗口的实例句柄 hInstance 3;如果将WM_CREATE交给系统处理的话。。系统做了什么 重复的说下过程吧,首先是定义一个窗口类,然后注册窗口类,然后依照你前边定义好的窗口类来创建一个窗口。以上就是CreateWindow函数调用结束前所做的事情,然后会调用ShowWindow将这个创建好的窗口显示出来。 接下来遇到消息循环了,程序开始从消息队列中取消息。 首先遇到的消息就是WM_CREATE(正如你所问到的),这条消息是由系统向你的应用程序投递的。然后系统会调用你写的那个“回调函数”,它依照你写的WM_CREATE内容来创建一些控件或子窗体。(注意在这个消息中并不创建主窗口,因为创建主窗口的工作早在ShowWindow前就创建好了) 在创建都完成以后就会把整个窗口的样子绘制出来,然后程序就可以处理别的消息啦。 至于第二个问题,它是得到不应用程序的实例句柄的。它的两个赋加参数中,wParam不会被使用,而lParam则是一个指向 CREATESTRUCT 结构体的指针,用于创建子窗体(控件)的一些必要信息。 你要想得到实例句柄要调用函数,最典型的就是GetWindowLong函数。利用该函数取应用程序实例句柄的调和方式如下: HINSTANCE hAppInstance = (HINSTACE)GetWindowLong(hwnd,GWL_HINSTANCE); 其参数有两个,第一个参数是主窗口的句柄,第二个参数是一个标记,代表你要取实例句柄,该函数还有别的功能,楼主可以自行查阅MSDN 还有其它的函数可以得到实例句柄,比如GetModuleHandle(NULL)的返回值也是实例句柄。 第三个问题已经在前边讲过了,注意你问的不对,这个消息本来就是由系统处理的,并不是你的程序处理的。