在開發windows引用程式的時候,在一些需要使用者確認,或者提示使用者注意的場合,經常使用模態對話方塊,或者叫模態視窗。在絕大多數情況下,模態視窗給開發人員帶來了極大的便利,並且在某些應用上有不可替代的優勢。然而凡事有利必有弊,如果不正確地使用模態視窗,卻有可能帶來某些嚴重問題,甚至可能引起程式崩潰。要想知道為什麼模態視窗可能帶來某些嚴重問題,就必須首先了解模態視窗的實現原理。因此本文將首先介紹模態視窗實現原理,然後分析為什麼會帶來問題。
知道了原理,一切就可迎刃而解。了解了原理,就可以知道,模態視窗並不是windows特有的,而是可以在任何乙個gui系統中實現出來,包括手機上。
因為windows上的模態對話方塊為眾人所知,因此本文的例子都是指windows上的,並且有時候會特指是mfc的。
眾所周知,當模態視窗被開啟之後,正常的流程會暫時掛起,或者通俗一點說,程式停住了,直到模態視窗關閉才會繼續執行。例如下面這段**:
cinputdialogdlg;
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,這個函式的第二個引數是lpmsglpmsg,這個正是從getmessage返回的當前訊息的結構體。可以想象,在dialogbox函式內部的實現裡,在呼叫isdialogmessage之前,必定先通過getmessage之類的函式,從訊息隊裡返回了當前的訊息了。
到了這裡,我們基本可以確定,在模態視窗內部,也實現了乙個訊息迴圈,真是這個訊息迴圈接管了執行緒中預設的訊息迴圈,使整個視窗系統能繼續正常的工作。同時由於訊息迴圈其實也是乙個有退出條件的死迴圈,因此到這個迴圈結束之前(一般是關閉了模態視窗),模態視窗後面的**是不會繼續執行的。
理解了模態視窗的原理,就可以在任何支援訊息佇列的gui系統中,加入模態視窗的機制,這會減少很多開發工作。例如很多手機平台不支援模態視窗,開發一些需要使用者確認的功能就比較麻煩,其實完全可以加入模態視窗,簡化開發。
模態視窗極大地簡化了一些需要和使用者互動的操作,好處顯而易見。但這裡還是要指出一些需要注意的地方,否則使用的時候很可能會出問題。
在使用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賦值,就會出現崩潰了。
這個問題,如果在多執行緒的情況下,將會更加嚴重。因為在多執行緒的情況下,將會有更加多的不可預料的因素,所以使用的時候要更加小心。
模態對話方塊可能導致程式崩潰
在開發windows引用程式的時候,在一些需要使用者確認,或者提示使用者注意的場合,經常使用模態對話方塊,或者叫模態視窗。在絕大多數情況下,模態視窗給開發人員帶來了極大的便利,並且在某些應用上有不可替代的優勢。然而凡事有利必有弊,如果不正確地使用模態視窗,卻有可能帶來某些嚴重問題,甚至可能引起程式崩...
模態對話方塊 非模態對話方塊 標準對話方塊 檔案對話方塊
模態對話方塊 qdialog 非模態對話方塊 qdialog 標準對話方塊 關於對話方塊 問題對話方塊等 qmessagebox 檔案對話方塊 qfiledialog 標準對話方塊還有 qcolordialog 選擇顏色 qfiledialog 選擇檔案或者目錄 qfontdialog 選擇字型 q...
模態對話方塊與非模態對話方塊
1.對話方塊分類 按工作方式不同,可將對話方塊分成兩類 模態對話方塊 modal 在關閉模態對話方塊之前,程式不能進行其他工作 如一般的 開啟檔案 對話方塊 非模態對話方塊 modeless 非模態對話方塊開啟後,程式仍然能夠進行其他工作 如一般的 查詢與替換 對話方塊 2.對話方塊建立 模態對話方...