阻礙關機的Modal Dialog [.Net 1.1]

來源:互聯網
上載者:User

I. 緣起

最近我的一位客戶跟我抱怨說,自從安裝了我做的軟體,他們本來應該半夜2點自動關機的伺服器,會偶爾在第二天早晨發現系統仍然開著。他們懷疑是我的軟體的Bug,希望我能檢查一下。

沒錯,無法自動關機是一個很煩人的事情:對於個人來說這意味著費電,電腦的磨損,心情的不爽等等;對企業來說這有可能意味著巨大的經濟損失。

可是,我的程式只是一個簡單的資料處理程式,它怎麼可能罪惡到阻撓關機呢?——我剛剛聽到客戶的抱怨時,我也這麼想;可是當我做了一個非常簡單的程式實驗之後,我得到非常令人沮喪的結果:
(注意,這是在.Net Framework1.1下的結果。在2.0已經修正了這個問題)

1,開啟Vistual Studio.Net,建立一個C#(VB.Net也可以,以下代碼以C#為例)的Windows Form。
2,假設自動產生的Form叫做Form1,我們再添加一個Windows Form叫做Form2。
3,在Form1上添加一個按鈕,在其Click事件處理函數中寫下:

Form2 f = new Form2();
f.ShowDialog();

 

沒錯,就是這麼簡單的一個程式——2個Form,在點擊第一個Form的按鈕時,把第二個Form作為Modal Dialog(強制回應對話方塊?)顯示。

好,現在編譯,運行這個程式(不要在調試環境下),然後按下那個按鈕,使Form2顯示出來。

然後關機(或者重新啟動,或者Log off也可以)。

你會發現,Form2關閉了,可是Form1還留在那裡;而且無論你等多久,還沒有出現你期待的關機(或者重啟,Logoff)。

如果是我們人類遇到這種情況,很簡單——我們可以反覆多試幾次;經驗豐富的高手還可以開啟工作管理員,尋找有可能是原因的進程,將其Kill之;最後實在無奈了還可以按下電源開關……可是很遺憾,我們的客戶不可能安排一個工作人員每天半夜2點爬起來一一驗證他們的Server關掉了沒有。

所以,我必須找到原因。

II. 發現

對於這個問題,我承認最開始我的確疏忽了——我當時覺得,.Net的Modal Dialog每天有成千上萬的人在使用;如果顯示了Modal Dialog就無法關機,肯定早就被人注意到了;也就是說,網上肯定有數不清的文章講解這個問題的原因和解決方案,並且,如果MS認為這算是一個BUG,會在MSDN中說明,給出解決方案;如果他就是這麼設計的,那麼他會給出理由的。——總之,這一定是一個Well-known的現象。

可是我找了一個小時卻一無所獲(也許是我用的Keyword選得不好?),所以我只好承認我的孤陋寡聞。可是無論我是多麼無知,我卻處於必須為客戶解決問題的尷尬位置上,所以我至少硬著頭皮嘗試解決這個問題。

作為分析這個問題的前提,有些粗淺的基礎知識是必須知道的;為了照顧和我一樣的菜鳥,我先把它們寫在這裡:

Windows在結束一次會話(指關機,重新啟動或者Logoff,以下稱“關閉”)時,會向所有視窗發送WM_QUERYENDSESSION通知(Int值是0x11,10進位的17)來詢問應用程式的意見,在收到“同意”的回答之後才會繼續關閉。所謂“同意”指的是對於這條訊息返回TRUE(或Int的1)。反之,如果一個程式在收到WM_QUERYENDSESSION之後返回了FALSE(或Int的0),那麼系統就會中斷關閉的動作並停止向其他程式發送關閉的通知,系統也就不會關閉了。

我們看到,在MSDN上寫道:預設情況下,對於這條訊息DefWindowProc函數將返回TRUE。(換句話說,任何沒有專門響應這條訊息的視窗,預設都是同意關閉的)

在.Net Framework中,相當於Win32體系中DefWindowProc的,就是Form.WndProc方法(當然還有其各個父類的WndProc方法)。其原型為:
protected override void WndProc(ref Message m);
其中,m是一個Message structure,其中的Msg代表訊息的類型,而Result正是對這條訊息的返回結果。詳細請參考MSDN。我們所關心的就是,在m.Msg = 0x11的情況下,預設的Form.WndProc到底給m.Result賦的什麼值。

系統在給視窗發送WM_QUERYENDSESSION詢問程式的意見後,系統會用WM_ENDSESSION來通知關閉動作的結果(究竟是詢問完所有程式再決定結果,還是詢問一個通知一個,這一點上Win9X和2K系列有若干區別,有興趣的朋友可以參看MSDN)。WM_ENDSESSION是0x16(10進位22)。同時WM_CLOSE(0x10,10進位16)也請記下。

另外,還有一點可能是題外話了。我們注意到上述現象僅在顯示一個Modal Dialog的情形下發生,而如果是用Show建立的Modeless Dialog則沒有任何問題。那麼我們查閱MSDN關於Form.ShowDialog方法的參考資料,可以看到MSDN上有如下介紹,我們不妨也記下來:
與modeless form不同,modal dialog在被使用者關閉的時候,.Net Framework不會調用其Close方法,而是將其隱藏(Hide)起來。這樣當你想再次顯示這個對話方塊的時候,你不必重新建立執行個體。但是同時也正因此,你在不再使用它的時候,需要調用其Dispose方法來回收資源。

好了,有了上面的基礎,我們就可以分析了。

用ildasm之類的反編譯工具開啟Windows.Form.dll,找到Form.WndProc方法並順藤摸瓜,我們很快就能找到與關閉視窗相關的幾條訊息都交給了一個叫做WmClose的方法處理。
該方法原型如下:
private void WmClose(System.Windows.Forms.Message m);

該方法的最開始便通過get_Modal()判斷當前視窗是否為Modal Dialog,如果是Modal Dialog,只是簡單地檢查DialogResult等等,之後就返回了;而如果是Modeless的,則要進行驗證、調用每個字視窗的OnClosing方法,最後綜合得出結果。下面是用虛擬碼寫的一個簡化版本(和實際情況稍有出入):

//Modal對話方塊時
if (Form.IsModal)
{
    if (Form.DialogResult == DialogResult.None)
    {
        Form.DialogResult = DialogResult.Cancel;
    }
}
else //Modeless對話方塊時
{
    //對於可取消得事件,都有這樣一個事件參數,將其Cancel property設為True即表明要取消該事件
    CancelEventArgs flag;

    //如果是Mdi容器
    if (Form.IsMdiContainer)
    {
        //對於每個Mdi子視窗
        foreach(childForm in Form.MdiChildren)
        {
            //調用其OnClosing方法,一旦有希望Cancel的方法就退出迴圈
            childForm.OnClosing(flag);
            if (flag.Cancel == true)
                break;
        }
    }
    //自己也要做一下關閉前的收尾工作
    Form.OnClosing(flag);

    //最後對於訊息的傳回值就是,如果有任何一個子視窗或本身表示Cancel,就返回0,大家全體通過了返回1
    Msg.Result = flag.Cancel ? 0 : 1;

通過上面的代碼,我們可以清楚地看到,在視窗為Modal的情況下,WndProc對於Message的Result沒有做任何修改,那麼也就是預設的0。換句話說:在.Net Framework下,Modal對話方塊顯示的時候將無法關閉系統。

III. 解決
我前面已經說過,由於我孤陋寡聞,不知道這個按理說是Well-known的問題;因此雖然通過分析IL知道了這的確是.Net Framework的邏輯使然,卻仍然不知道這是MS故意這麼做的,還是有什麼特別的說法,或者是一個Bug。

不過,既然在C++中使用DialogBox宏產生的Modal Dialog是不會阻撓關機,MSDN上也說了DefWindowProc對WM_QUERYENDSESSION將返回TRUE,因此至少可以說.Net Framework 1.1下的Modal Dialog違背了以往的原則。

解決這個問題非常簡單:只需要重載WndProc方法,當Form被以Modal方式顯示,並且接收到WM_QUERYENDSESSION訊息時令訊息的傳回值等於1即可。
由於我們已經分析過預設的WndProc的IL代碼,因此我們可以確認這樣做不會有任何問題。

另外,在.Net Framework 2.0當中,已經對這個問題進行了修正。有興趣的朋友可以自己看一下2.0當中WndProc的IL代碼(嫌讀IL麻煩的直接用Reflector之類的吧)。

IL看得比較匆忙,若有錯漏之處,還請指正。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.