模態對話方塊可能導致程式崩潰
在開發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機制
在使用MFC,WTL等進行開發的時候,經常用到PreTranslateMessage機制,這個機制可以讓我們在訊息被派發之前先做一些事情。很多人以為PreTranslateMessage是Windows本身支援的,其實不然。PreTranslateMessage是MFC和WTL自己引入的一個概念,完全是和Windows無關的。在MFC和WTL的訊息迴圈中,這兩個庫的設計者在訊息分發之前,人為的加了一些程式碼,使得整個架構支援這一套機制。
正是如此,如果在正常的流程中彈出了模態視窗,就會使正常的PreTranslateMessage機制失效。因為模態視窗中已經包含了一個訊息迴圈,接管了執行緒中預設的訊息迴圈。而這個訊息迴圈是在DialogBox這個API函式中執行的,顯然不可能再有PreTranalateMessage機制了。
為了解決這一問題,只有讓模態視窗也使用和UI執行緒相同的訊息迴圈,MFC正是這麼做的。在MFC中,對話方塊類的DoModal函式,並不是呼叫DialogBox函式,而是直接使用CreateWindows建立一個非模態視窗,在視窗建立成功之後再呼叫MFC自己的訊息迴圈,這樣就可以讓PreTranslateMessage繼續生效。同時在視窗建立出來之後,必須再做一些別的操作,使這個模態視窗的父視窗失效(一般直接把視窗Disable掉)。同時訊息迴圈裡有合適的退出條件,並有恢復現場的一些操作,具體可以檢視MFC的DoModal函式。
WTL到目前為止,貌似暫時還沒有一個合適的方案來解決這個問題。事實上WTL的PreTranslateMessage機制實現的其實是有點問題的,或許以後會在這方面做一定的增強。
可能導致崩潰
這是一個嚴重問題,在條件合適的情況下,這個崩潰是必然的。
因為模態視窗彈出來之後,模態視窗後面的程式碼在視窗關閉之前將不會得到執行。然而此時整個視窗是在正常執行的,對於一些極端的情況,是極有可能造成崩潰的。下面看一個例子:
void CTestDlg::OnOK()
{
CInputDialog dlg;
If(dlg.DoModal() == IDOK)
{
m_nValue = dlg.GetValue();
UpdateData(FALSE);
}
}
這是一段典型的MFC程式碼,在絕大多數情況下,不會有任何問題。但是由於模態視窗彈出的時候,只是父視窗不能操作,但別的視窗完全還能正常執行,這時候就非常有可能由於某種原因,CTestDlg類已經銷燬了,而CInputDialog卻不知道,還在繼續執行,結果到了IDOK之後,對CTestDialog類的成員變數m_nValue賦值,就會出現崩潰了。
這個問題,如果在多執行緒的情況下,將會更加嚴重。因為在多執行緒的情況下,將會有更加多的不可預料的因素,所以使用的時候要更加小心。
相關文章
- 【C++】【MFC】模態和非模態對話方塊C++
- 模態和非模態對話方塊(簡單易懂!)
- iOS開發-stringByEvaluatingJavaScriptFromString導致崩潰iOSJavaScript
- 執行緒崩潰為什麼不會導致 JVM 崩潰執行緒JVM
- Dialog對話方塊
- 多塊硬碟離線導致raid6崩潰的資料恢復案例硬碟AI資料恢復
- A站大流量導致服務崩潰異常分析
- 誤升級GLIBC導致系統崩潰之後
- 記一次 .NET某工控 宇宙射線 導致程式崩潰分析
- 登入對話方塊
- 規定對話方塊
- node啟動程式-清理由於崩潰導致的沒有關掉的程式
- 解決Qt中ui->tableView->setModel(model);導致程式崩潰 問題QTUIView
- memcopy 導致的程式碼崩潰問題,memcpy的三大踩坑記memcpy
- 儲存崩潰導致資料丟失如何處理
- DialogPane對話方塊佈局
- Flutter Widgets 對話方塊-DialogFlutter
- flutter demo (四):對話方塊Flutter
- java Swing詢問對話方塊Java
- 在 Flutter 使用 GetX 對話方塊Flutter
- Qt 對話方塊新增工具欄QT
- 0x7_對話方塊
- 訊息對話方塊 confirm() prompt()
- React中的模式對話方塊React模式
- VUE:點選開啟的對話方塊外面時,對話方塊總是被關閉Vue
- 伺服器磁碟離線導致RAIDZ崩潰資料恢復伺服器AI資料恢復
- 記錄一個LifeCycle 多執行緒使用導致的崩潰執行緒
- 如何自學qt(4)——對話方塊QT
- alicdn邊緣節點不穩定導致頁面崩潰問題
- 微軟修復了導致 Outlook 啟動時崩潰的問題微軟
- HttpClient引發的執行緒數過多導致應用崩潰HTTPclient執行緒應用崩潰
- 案例解析:執行緒池使用不當導致的系統崩潰執行緒
- 新的Firefox bug可導致瀏覽器程式與作業系統一同崩潰Firefox瀏覽器作業系統
- 直播系統程式碼,自定義平臺私聊對話方塊
- 【北亞企安資料恢復】RAIDZ多塊磁碟離線導致崩潰的資料恢復案例資料恢復AI
- lol關於win10系統導致閃退崩潰修復方法Win10
- 蘋果iOS 11.3/11.4曝bug:“黑點錯誤”導致裝置崩潰蘋果iOS
- JavaFX 如何使用內建的對話方塊Java