今天工作不忙,想起几年前一个同事提起的一个关于MessageBox的问题,其实你们可以理解成是模式对话框的问题或者说是对话框消息处理机制的问题,问题描述如下:1、MessageBox并不会创建新的线程(可以用任务管理器证实)
2、调用MessageBox会阻挡程序进一步执行,从代码上看是起到堵塞的作用(写过程序的人都知道)
3、MessageBox并不会影响消息循环(程序代码上虽然“堵塞”,但窗口并没有失去响应)
4、MessageBox并不会释放CPU时间(如Sleep之类)(程序没失去响应)
5、MessageBox可以同时弹出多个(使用一些手段就很容易做到)那么:
1、MessageBox如何实现对代码的堵塞?
2、如果弹出多个MessageBox,是不是可以算多重堵塞?而这个堵塞明显和WaitForSingleObject是不同的。(并没有释放CPU资源)
3、如果说模式对话框接管了父窗口的消息处理权,那么我可以强制Enable父窗口,父窗口其实还是有响应的,那这个“堵塞”究竟堵塞的是什么?
4、windows内部如何实现这个机制?恳请大家用心思考,用心回答。BTW:对csdn.net没有专门的Windows编程讨论专区表示抗议,对csdn.net把VC和MFC混为一体表示抗议。

解决方案 »

  1.   

    MessageBox函数,以及AfxMessageBox函数都是阻塞原有的消息循环的。由消息框内部的一个消息循环来从消息队列中读取消息,并派送消息(和模式对话框类似)。实际上,这些消息函数最终调用的是::MessageBox,它在消息框内部实现了一个消息循环(原有的主程序消息循环被阻塞了)。 
      

  2.   

    to debehe :循环没有被“阻塞”,你可以尝试把父窗口强制设置为Enable,用::EnableWindow(hwndParent, TRUE);
    你会发现一点问题都没有,界面的“模式”,只是把父窗口Disable掉。
      

  3.   

    striking你这个两星级的家伙也不替我多想想啊,蒋老大不一定在啊。继续看书……
      

  4.   


    debehe 说的对。
    楼主说的EnableWindow(false)掉主窗口也对,因为windows确实就是这么做的,不过这个和主线程的消息循环被阻塞掉没有冲突。
    从代码上看,就是调用messagebox的函数没有返回,主线程还在等待该函数的返回。
      

  5.   

    模式对话框都有自己的消息循环,它阻塞的是原始的消息循环,但是被对话框的消息循环接替。消息循环的本质是调用窗口过程,进一步调用你的各种消息响应函数,所以无论有多少个消息循环存在,只要有一个消息循环有效,所有的消息响应函数都能被调用,这也是为什么主窗口还能响应消息的缘故。多个消息循环的存在会产生某些副作用,比如消息重入,第一次消息响应时弹出一个模式对话框,模式对话框的消息循环2取代原始消息循环1,假设此时主窗口消息队列里又有一个同样的消息到来,消息循环2也会调用同样的窗口过程(响应函数),此时就能导致消息重入(因为第一次进入这个处理函数还没有返回,本质上这个响应函数被递归调用了),这也是能弹出多个模式对话框的原因。每个模式对话框都有自己的消息循环,只有最后一个弹出对话框的消息循环才是活动的消息循环,其它所有消息循环(包括主窗口、之前弹出的模式对话框)全被阻塞。这里的“阻塞”并不是消息被阻塞,而只是DispatchMessage一直没有返回而已,但是其它的消息循环会接替这些工作。
      

  6.   

    确实如胡柏华所说,我正在用代码证实,在自己用代码实现MessageBox,有问题再提问。
      

  7.   

    MessageBox 只是禁用他的父窗口
      

  8.   


    先只是禁父窗口 , 进入自己的消息循环, 鼠标的输入消息 是被发送到具有光标的窗口中的,当然了键盘也是(键盘输入焦点),
    如果, EnableWindow(hwndParent,   TRUE)了, 只是不再禁用父窗口了,就成了一种 非模态窗口
      

  9.   


    先只是禁父窗口 , 进入自己的消息循环, 鼠标的输入消息 是被发送到具有光标的窗口中的,当然了键盘也是(键盘输入焦点),
    如果, EnableWindow(hwndParent,   TRUE)了, 只是不再禁用父窗口了,就成了一种 非模态窗口每个窗口都有自己的消息循环 ,(由于禁父窗口 键盘和鼠标的捕获),是我们以为 “阻塞的是原始的消息循环,但是被对话框的消息循环接替"其实, 你是你 , 他是他, 
      

  10.   

    这个帖子跟http://topic.csdn.net/u/20071225/22/c469bef8-f58f-4017-a630-bda09777b3b8.html讨论的是同一个问题,很值得思考。
    我对胡柏华说的有点疑问。函数在同一个线程中是无法“重入”的,因为在没有从函数返回之前无法进行下一次调用。正因为如此,如果线程在某个点上停住后(调用SleepEx,MsgWaitForMultipleObjectsEx等把控制权交还给内核不算停住),该线程会阻塞无法响应消息,看起来就是“死掉了”。这也是MsgWaitForMultipleObjectsEx等函数存在的原因,当线程运行时,你是无法调用该线程另一个函数的,MsgWaitForMultipleObjectsEx等函数就是给系统机会在本线程中运行一个回调函数。
    对这个问题我的理解是这样的:
    windows的消息是在各个窗口间流动的,父窗口和子窗口在同一个线程中,系统发给线程的消息它们都可以选择处理或者放弃。但是一旦进行了处理,在处理未结束时是不能接受新的消息的。另一个帖子所说的Timer问题,其实Timer仅仅是另一种消息而已,跟OnCommand(ID_MYMENU)没什么不同。当弹出对话框后,大部分消息是由对话框处理了,而父窗口选择丢弃消息,所以父窗口表现为"disabled"。但是一旦遇到Timer消息,父窗口不传递给对话框,而是自行处理。程序就再这样的接收消息-〉分发消息-〉处理消息-〉返回控制权给系统的循环中进行。
    我没做过试验,所以我说的不是100%可靠,不过试验很简单,set一个Timer,在Timer中不断向一个文件写入当前时间。弹出一个对话框,你应该能发现时间还是不断更新的。然后再试试让对话框的一个消息响应函数进入死循环,你会发现时间不再更新。等我有空的时候再试试看吧。
      

  11.   

    to superdiablo:
    递归函数调用就是一个最典型的函数重入,在函数体内再次调用本函数。只要函数堆栈不溢出,你可以一直递归下去。消息循环跟这个原理是一样的。可以这样做试验:不停向主窗口发送同一个消息,每个消息弹出一个模式对话框,当积累到一定次数时,会发生堆栈溢出报错。Sleep/Wait...之类的函数导致的是线程的阻塞,但是消息循环并未阻塞线程执行,而是一直在运行代码,当然GetMessage调用会阻塞线程,但PeekMessage不会,但是只要有消息存在,就能假设线程阻塞是不存在的。
      

  12.   

    to   superdiablo: 谁说同一线程函数无法重入?我举例子说一下具体流程:
     1主线程调用getmessage()接收消息wm_user1,并调用窗口函数,窗口函数根据消息调用处理函数on_dealuser1();
     2该消息wm_user1的处理函数on_dealuser1()引发了一个messagebox(),由于messagebox没有返回,所以on_dealuser1()不会返回,所以窗口函数不会返回,所以主线程的getmessage()不会返回!
     3messagebox又有一个getmessage(),负责接收消息;
     4如果这时又来了一个wm_user1消息给主窗口,那么:messagebox的getmessage()会负责接收这个消息,并派发给主线程的窗口函数,这时on_dealuser1()又会被调用!
      那么on_dealuser1()就被重入了!
      

  13.   

    to cymvp :事实大致如你所说我现在研究弹出多个MessageBox导致的副作用。
      

  14.   

    哦我想错了,那应该比较清楚了吧,另一个帖子所说的WM_TIMER消息,应该也是在主程序的消息循环中处理的吧。
    等有空做一下实验看看
      

  15.   

    呵呵,迟到了有三个线程:一个UI线程(主窗口)(且称为线程1)、一个主窗口创建的work线程(线程2)、还有另外一个独立的work线程(线程3)。一开始线程1和线程3并行工作,但线程3要更新线程1界面的控件,线程1创建的线程2由于某种原因执行MessageBox,hwnd为NULL,但这时主线程似乎被阻塞了,进而导致线程3也被阻塞。请问工作线程调用hwnd为NULL的MessageBox会导致主界面消息循环阻塞吗?