MFC
終了がログオフなのか電源を切る/再起動なのかを判定する


HomeProgramming TipsMFC Tips[MFC-019]

Windows が終了するときは WM_ENDSESSION が呼び出されます。
MFC では、ON_WM_ENDSESSION() として専用のメッセージマップマクロが用意されています。
メンバ関数としては void CFooWnd::OnEndSession(BOOL bEnding) が用意されています。
bEnding が真(!=FALSE)なら Windows が終了するタイミングです。

ところで、Windows の終了には、ログオフ/電源を切る/再起動の3種類があります。
OS にとっては、電源を切るのも再起動も、どちらも BIOS レベルまで戻りますので同じ事です。
と、なると、ログオフか、そうでないかを判断してプログラムを組みたいことがあるはずです。

Windows の終了時は、本当に終了しても良いかを起動中の各アプリに問い合わせをしています。
その問い合わせメッセージが WM_QUERYENDSESSION です。
この終了確認メッセージに対して、許可なら TRUE、拒否なら FALSE とすれば制御ができます。
このメッセージには、実は、どのような終了の仕方をするかの情報があります。
LPARAM の値に対して、ENDSESSION_LOGOFF を AND 演算します。
この演算結果が真(!=FALSE)なら、その終了はログオフという事になります。

WM_QUERYENDSESSION も標準でメッセージマップマクロが用意されています。
それが ON_WM_QUERYENDSESSION() なのですが、これが少々問題があるのです。
このメンバ関数を確認すると BOOL CFooWnd::OnQueryEndSession(void) となっています。
つまり LPARAM が捨てられているのです。これではログオフの確認に使えません。

そのため、自力でメッセージをマップすることで問題を解決します。
まず、h ファイル内にプロトタイプを記述します。

■ CFooWnd.h

    afx_msg LRESULT OnQueryEndSession(WPARAM, LPARAM lEndReason);


次に cpp ファイルのメッセージマップに OnQueryEndSession を追加します。
マップするメッセージは WM_QUERYENDSESSION です。

■ CFooWnd.cpp

BEGIN_MESSAGE_MAP(CFooWnd, CWnd)
    ON_MESSAGE(WM_QUERYENDSESSION, OnQueryEndSession)
END_MESSAGE_MAP()


これで、WM_QUERYENDSESSION の LPARAM 値が届くようになりました。
実際に終了が確定するのは OnEndSession のタイミングです。
そのため、OnQueryEndSession の情報を一旦メンバ変数に保存します。

■ CFooWnd.h/CFooWnd クラス定義内

private:
    // ログオフか?
    BOOL m_bEndLogOff;


このメンバ変数 m_bEndLogOff は private が望ましいです。
一応、念のためコンストラクタで FALSE にでも初期化しておいてください。

OnQueryEndSession で終了種別を判定してメンバ変数に格納します。

■ CFooWnd.cpp

// ---------------------------------------------------------------------
// Windows 終了の確認メッセージを受け取った
// ---------------------------------------------------------------------

LRESULT CFooWnd::OnQueryEndSession(WPARAM, LPARAM lEndReason)
{
    m_bEndLogOff = BOOL(lEndReason & ENDSESSION_LOGOFF);
    return TRUE;
}


これで終了種別の確認ができました。
後は、Windows の終了通知メッセージにて m_bEndLogOff の値に応じて処理を行うようにします。


// ---------------------------------------------------------------------
// Windows が終了するかの通知メッセージを受け取った
// ---------------------------------------------------------------------

void CBaseWnd::OnEndSession(BOOL bEnding)
{
    CWnd::OnEndSession(bEnding);

    if (bEnding) {
        // Windows 終了が確定した
        if (m_bEndLogOff) {
            // ログオフ時の処理を記述する
        } else {
            // 電源を切る/再起動時の処理を記述する
        }
    }
}


■ 注意!
Win95(IE4)、Win98、WinMe では WM_ENDSESSION よりも早く WM_DESTROY が呼び出されてしまうようです。
そのため、OnQueryEndSession の次に OnDestroy が続けてきたら、OnDestroy 内で終了用の処理をします。
本当は OnDestroy で何かしらの処理を行うことはあまりよろしくないのですが…他に方法が思いつきません…。
※ すいません Win98 系は既に確認できないので自分で動作を確認できていません。



 Copyright 2005 VALGUS. All Rights Reserved.