`
shfzhzhr
  • 浏览: 70582 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

模态对话框可能导致程序崩溃

阅读更多

在开发Windows引用程序的时候,在一些需要用户确认,或者提示用户注意的场合,经常使用模态对话框,或者叫模态窗口。在绝大多数情况下,模态窗口给开发人员带来了极大的便利,并且在某些应用上有不可替代的优势。然而凡事有利必有弊,如果不正确地使用模态窗口,却有可能带来某些严重问题,甚至可能引起程序崩溃。要想知道为什么模态窗口可能带来某些严重问题,就必须首先了解模态窗口的实现原理。因此本文将首先介绍模态窗口实现原理,然后分析为什么会带来问题。

原理

 

知道了原理,一切就可迎刃而解。了解了原理,就可以知道,模态窗口并不是Windows特有的,而是可以在任何一个GUI系统中实现出来,包括手机上。

因为Windows上的模态对话框为众人所知,因此本文的例子都是指Windows上的,并且有时候会特指是MFC的。

众所周知,当模态窗口被打开之后,正常的流程会暂时挂起,或者通俗一点说,程序停住了,直到模态窗口关闭才会继续执行。例如下面这段代码:

CInputDialog  dlg;

if(dlg.DoModal() == IDOK)

{

// 执行按了确定按钮退出的流程

}

else

{

// 执行通过别的方式退出的流程,例如按了取消按钮

}

// 继续执行

在这段代码里,在CInputDialog窗口关闭之前,注释部分的代码是不会得到执行的。

接下来请先思考一个问题,为什么调用了dlg.DoModal()之后,程序会停住呢?

首先,不可能是线程被挂起,因为一般情况,只有一个主线程,如果线程挂起,那就什么也做不了了,但显然模态窗口弹出来之后还是可以做很多事情的。

其次,也不可能是用类似于Sleep之类的函数,让程序等待,和线程挂起一样。

如果我们了解Windows应用程序的运行的原理,了解消息分发的机制,就可以知道,UI线程有一个消息循环,通过GetMessage之类的函数获取消息,并且分发。如果没有这个消息循环,整个窗口系统就无法正常工作。很显然,当有模态窗口打开的时候,整个窗口系统还是正常工作的,因此可以确定,此时消息循环一定还在正常运行着。这个消息循环在哪里呢?因为当模态对窗口弹出来之后,程序就暂停了,相当调用模态窗口的函数一直没有返回,那么也就没有机会再进入缺省消息循环了,这到底是怎么回事呢?福尔摩斯经常说:“除去不可能的剩下的即使再不可能,那也是真相。”基于这个道理,真像只有一个,就是模态窗口内部有一个消息循环,负责消息的接收和转发。

为了证明这个说法,可以做个试验,弹出一个模态对话框,并设置合适的断点,查看堆栈。

使用DialogBox(NULL, MAKEINTRESOURCE(IDD_MAINDLG), m_hWnd, DialogProc);语句弹出对话框,并且在DialogProc里设置一个合适的断点,我们可以在堆栈中看到这样的信息:

         ZK.exe!CMainDlg::DialogProc(HWND__ * hwndDlg=0×000411e0, unsigned int uMsg=0×00000201, unsigned int wParam=0×00000001, long lParam=0×003a009c) 90 C++

        user32.dll!_InternalCallWinProc@20() + 0×23 字节     

        user32.dll!_UserCallDlgProcCheckWow@32() + 0xa9 字节        

        user32.dll!_DefDlgProcWorker@20() + 0×7f 字节       

        user32.dll!_DefDlgProcW@16() + 0×22 字节        

        user32.dll!_InternalCallWinProc@20() + 0×23 字节     

        user32.dll!_UserCallWinProcCheckWow@32() + 0xb3 字节      

        user32.dll!_DispatchMessageWorker@8() + 0xe6 字节     

        user32.dll!_DispatchMessageW@4() + 0xf 字节 

        user32.dll!_IsDialogMessageW@8() - 0xeaa7 字节 

        user32.dll!_DialogBox2@16() + 0xc0 字节    

        user32.dll!_InternalDialogBox@24() + 0xb6 字节        

        user32.dll!_DialogBoxIndirectParamAorW@24() + 0×36 字节   

        user32.dll!_DialogBoxParamW@20() + 0×3f 字节        

        ZK.exe!CMainDlg::OnOK(unsigned short __formal=0×0000, unsigned short wID=0×0001, unsigned short __formal=0×0000, unsigned short __formal=0×0000) 98 + 0×1d 字节 C++

上面的堆栈信息中,红色加粗的函数是API函数IsDialogMessage,这个函数的第二个参数是LPMSG lpMsg,这个正是从GetMessage返回的当前消息的结构体。可以想象,在DialogBox函数内部的实现里,在调用IsDialogMessage之前,必定先通过GetMessage之类的函数,从消息队里返回了当前的消息了。

到了这里,我们基本可以确定,在模态窗口内部,也实现了一个消息循环,真是这个消息循环接管了线程中缺省的消息循环,使整个窗口系统能继续正常的工作。同时由于消息循环其实也是一个有退出条件的死循环,因此到这个循环结束之前(一般是关闭了模态窗口),模态窗口后面的代码是不会继续执行的。

理解了模态窗口的原理,就可以在任何支持消息队列的GUI系统中,加入模态窗口的机制,这会减少很多开发工作。例如很多手机平台不支持模态窗口,开发一些需要用户确认的功能就比较麻烦,其实完全可以加入模态窗口,简化开发。

注意事项

模态窗口极大地简化了一些需要和用户交互的操作,好处显而易见。但这里还是要指出一些需要注意的地方,否则使用的时候很可能会出问题。

影响PreTranslateMessage机制

在使用MFCWTL等进行开发的时候,经常用到PreTranslateMessage机制,这个机制可以让我们在消息被派发之前先做一些事情。很多人以为PreTranslateMessageWindows本身支持的,其实不然。PreTranslateMessageMFCWTL自己引入的一个概念,完全是和Windows无关的。在MFCWTL的消息循环中,这两个库的设计者在消息分发之前,人为的加了一些代码,使得整个架构支持这一套机制。

正是如此,如果在正常的流程中弹出了模态窗口,就会使正常的PreTranslateMessage机制失效。因为模态窗口中已经包含了一个消息循环,接管了线程中缺省的消息循环。而这个消息循环是在DialogBox这个API函数中执行的,显然不可能再有PreTranalateMessage机制了。

为了解决这一问题,只有让模态窗口也使用和UI线程相同的消息循环,MFC正是这么做的。在MFC中,对话框类的DoModal函数,并不是调用DialogBox函数,而是直接使用CreateWindows创建一个非模态窗口,在窗口创建成功之后再调用MFC自己的消息循环,这样就可以让PreTranslateMessage继续生效。同时在窗口创建出来之后,必须再做一些别的操作,使这个模态窗口的父窗口失效(一般直接把窗口Disable掉)。同时消息循环里有合适的退出条件,并有恢复现场的一些操作,具体可以查看MFCDoModal函数。

WTL到目前为止,貌似暂时还没有一个合适的方案来解决这个问题。事实上WTLPreTranslateMessage机制实现的其实是有点问题的,或许以后会在这方面做一定的增强。

可能导致崩溃

这是一个严重问题,在条件合适的情况下,这个崩溃是必然的。

因为模态窗口弹出来之后,模态窗口后面的代码在窗口关闭之前将不会得到执行。然而此时整个窗口是在正常运行的,对于一些极端的情况,是极有可能造成崩溃的。下面看一个例子:

void CTestDlg::OnOK()

{

         CInputDialog dlg;

         If(dlg.DoModal() == IDOK)

         {

                   m_nValue = dlg.GetValue();

                   UpdateData(FALSE);

         }

}

这是一段典型的MFC代码,在绝大多数情况下,不会有任何问题。但是由于模态窗口弹出的时候,只是父窗口不能操作,但别的窗口完全还能正常运行,这时候就非常有可能由于某种原因,CTestDlg类已经销毁了,而CInputDialog却不知道,还在继续执行,结果到了IDOK之后,对CTestDialog类的成员变量m_nValue赋值,就会出现崩溃了。

这个问题,如果在多线程的情况下,将会更加严重。因为在多线程的情况下,将会有更加多的不可预料的因素,所以使用的时候要更加小心。

分享到:
评论

相关推荐

    PreTranslateMessage中调用DoModal出错的解决

    这是一个合理的解决方案,因为它确保了在模态对话框显示期间,不会处理额外的消息,从而避免了可能的父窗口无效问题。 #### 代码示例 以下是修改后的`PreTranslateMessage`函数示例: ```cpp BOOL CInergyWriteDlg:...

    VC++同时关闭多个子对话框!!

    无论哪种方法,都要注意处理好资源释放、异常安全和线程同步等问题,以避免内存泄漏或程序崩溃。 总的来说,VC++中同时关闭多个子对话框涉及到对Windows API的熟练掌握,对MFC框架的理解,以及对多线程和消息机制的...

    多线程解决mfc对话框未响应、卡死问题

    确保每个线程都能正确处理异常,防止异常传播导致整个程序崩溃。使用try-catch结构捕获并处理异常。 **8. 线程退出** 当不再需要工作线程时,不要直接杀死线程,而是应该让线程自行结束。可以设置一个标志,线程在...

    RevitAppTemplate

    6. **错误处理**:在实现External Event时,错误处理至关重要,因为任何未捕获的异常都可能导致Revit崩溃。因此,需要在事件处理函数中添加适当的异常处理代码。 7. **调试技巧**:由于Revit插件是在Revit环境中...

    vc++6.0入门与提高

    **注意**:使用`sprintf`时需确保缓冲区足够大,以免发生溢出导致程序崩溃或安全问题。 #### 单文档程序SDI架构详解 单文档接口(Single Document Interface, SDI)是一种应用程序设计模式,其中一次只处理一个...

    MyThreadDlg.rar_Dialogue

    - 在线程编程中,错误处理是必不可少的,因为线程间通信的问题可能导致程序崩溃。调试工具如Visual Studio的调试器可以帮助识别和解决这些问题。 综上所述,"MyThreadDlg.rar_Dialogue"中的示例可能是一个演示如何...

    select头像选择代码.rar

    错误处理可以提供更顺畅的用户体验,减少因意外情况导致的程序崩溃。 总结起来,“select头像选择代码”涉及到前端开发中的JavaScript交互设计、DOM操作、用户反馈机制、图像处理以及可能的服务器通信等多个方面。...

    多任务、进程和线程.doc

    如果程序长时间不调用这些函数,会导致系统无法响应其他应用和用户输入,从而可能造成系统挂起。 为了解决这个问题,程序员通常会设计非模态对话框,并在其中分批执行后台操作,以便在执行长时间任务时仍能响应用户...

    《Visual c++ MFC 编程实例》

    这有助于提高程序的健壮性,防止因未捕获的异常导致程序崩溃。 10. **多线程编程**:MFC支持多线程应用程序,通过CWinThread类可以创建和管理线程。多线程编程在处理大量计算任务或并发操作时非常有用。 压缩文件...

    关闭MFC窗体

    2. **控件销毁与数据同步**: 如果在对话框关闭前已经销毁了一些控件(例如通过 `DestroyWindow` 函数),此时若尝试调用 `OnOK()` 关闭对话框,则可能会导致程序崩溃。因此,在销毁控件后,应避免调用涉及这些控件的...

    MFC简易计算器英文版

    8. **异常处理**:为了确保程序的健壮性,计算器可能会包含异常处理代码,防止如除以零等错误导致程序崩溃。 9. **编译与调试**:使用VC++ 6.0编译器,开发者可以编写、编译和调试代码。虽然这个版本相对较老,但它...

    PopupWin.dll消息控件

    - 它可能被各种应用程序调用,以实现非模态对话框的快速显示,例如消息提示、警告对话框等。 - 在某些情况下,PopupWin.dll也可能会用于自定义的用户界面元素,提供更丰富的交互体验。 3. **使用PopupWin.dll** ...

    MFC 浮点数计算器 经多次修正 计算精度高

    2. **对话框类(CDialog)**:计算器通常是一个模态或非模态对话框,因此会使用CDialog类或者其派生类。对话框模板定义了计算器的布局,包括控件的位置和大小。 3. **控件类(CButton, CEdit等)**:MFC提供了许多...

    易语言载入窗口模态源码-易语言

    6. **错误处理**:在源码中加入错误处理机制是非常重要的,可以避免因异常情况导致程序崩溃。易语言提供了`错误捕获`和`错误恢复`等指令来处理可能出现的错误。 7. **调试与测试**:源码编写完成后,需要通过调试...

    易语言-易语言超文本浏览框/网页拦截弹窗

    4. **错误处理**:考虑到网页内容的复杂性,需要有良好的错误处理机制,防止因无法正确处理某些页面而导致程序崩溃。 通过这样的实现,开发者可以更好地控制程序内嵌网页的行为,提供更加一致和流畅的用户体验。...

    让人震惊的10个非技术人员无法理解的软件概念

    例如,模态对话框(Modal Dialog)的设计就非常讲究——何时使用、如何展示等问题都需要仔细考虑。对于非技术人员而言,理解这些设计原则背后的逻辑并非易事。 ### 5. 错误处理与调试 错误处理是指软件在遇到异常...

    JQuery Dialog的内存泄露问题解决方法

    在编程中,内存泄露是指程序在申请内存后,未能在不再需要时释放,导致这部分内存无法被操作系统或其他程序使用,最终可能导致程序可用内存不足,影响程序性能甚至导致程序崩溃。 对于JQuery Dialog来说,问题可能...

    C++ MFC实现飞机大战游戏

     AfxMessageBox()是模态对话框,你不进行确认时程序是否往下运行时,它会阻塞你当前的线程,除非你程序是多线程的程序,否则只有等待模态对话框被确认。  在MFC中,afxmessagebox是全局的对话框最安全,也最方便。...

    systematic error handling in c++

    每一种错误场景都要求程序员能够预见并处理各种异常情况,以避免程序崩溃或数据损坏。 可靠性场景则包括程序错误、意外的程序状态、损坏的I/O、核心内存故障等。对这些情况的处理往往是程序健壮性的关键。例如,...

Global site tag (gtag.js) - Google Analytics