Windows API でシャットダウン要求を強制無視(無効化)する (Vista, 7)

  • 8
    Like
  • 0
    Comment

常駐アプリでシャットダウンの続行を止めたい人は、下記 Don't Sleep を使えば良いようです。

しかし、「自作のプログラムでも同様にシャットダウンを抑制したい!」という人のために、コードを書いておきたいと思います。ここで言う抑制とは、同アプリと同じく「アプリ起動中はとにかく一切シャットダウンさせない(禁止・無効化)」とかそんな類のものです。

シャットダウンを抑止しても良いの?

Application Shutdown Changes in Windows Vista (Windows) を見ると「XP 以前では、アプリケーション都合でユーザーのシャットダウン要求が妨げられてたみたいだから、Vista で挙動とか変えたよ~」的なことが書いてあります。「シャットダウンを妨げるんじゃなくて、極力シャットダウンを素早く続行できるように協力しなさい!」的なことが書いてあります。(ものすごい意訳ですね)

本当にシャットダウンの抑止が必要なのか、一度よく考えましょう。また、多かれ少なかれシャットダウンを抑止する際は、上記のガイドを一読すべきです。表示メッセージに関するガイドなど、さまざまな情報があります。

シャットダウン等を抑止する

下記の手順で実現できました。

  1. SetProcessShutdownParameters で他のプロセスよりも優先して終了処理が行われるように設定する
  2. WM_QUERYENDSESSIONFALSE を返し、終了処理を遅延したいことを示す

また、下記も併用すると良いかもしれません。

  • SetThreadExecutionStateES_SYSTEM_REQUIRED を指定して、コンピュータがスタンバイ状態になるのを防ぐよう設定する

WM_QUERYENDSESSIONFALSE を返したあと、どのくらい持つのか不明ですが、1時間くらい放っておいても平気でした。「強制的にシャットダウン」するとシャットダウンが続行されますし、そこまでされたらおとなしく終了すべきかと思いますが、その場合でも WM_ENDSESSION で終了処理を行う猶予が数秒間与えられます。

挙動確認用のソースコードの抜粋を下記に記します。

#include <windows.h>
#include <tchar.h>
#include <stdarg.h>
#include "resource.h"

#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))

HWND hMainWnd = NULL;

void SetupShutdownBlock(bool noStandby, bool noScreenSaver);
void OutputDebugLog(LPCTSTR format, ...);

// Block Windows standby / screensaver.
// スタンバイや画面オフを無効化
void SetupPowerOffBlock(bool noStandby, bool noScreenSaver)
{
    // Get OS version
    // OS のバージョンを取得
    OSVERSIONINFO osVersion;
    osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&osVersion);

    // Set shutdown order (highest priority)
    // シャットダウン時の処理順序を最高にする
    DWORD priorityLevels[5] = { 0x4FF, 0x4F0, 0x3FF, 0x3F0, 0x300 };
    for (int i = 0; i < ARRAY_LENGTH(priorityLevels); i++)
    {
        if (SetProcessShutdownParameters(priorityLevels[i], 0) != FALSE)
        {
            break;
        }
    }

    if (noScreenSaver)
    {
        // Windows 7 or later?
        if (osVersion.dwMajorVersion == 6 && osVersion.dwMinorVersion >= 1)
        {
            // Cast a magic spell, I'm not sure why it is needed.
            // なんだろうこれ、おまじない
            BOOL screenSaveActive;
            SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &screenSaveActive, 0);
            SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, screenSaveActive, NULL, SPIF_SENDWININICHANGE);
        }
    }

    // Protect process from standby or screensaver.
    // プロセスをスタンバイや画面オフから守るように命じる
    EXECUTION_STATE state = ES_CONTINUOUS |
        (noStandby ? ES_SYSTEM_REQUIRED : 0) |
        (noScreenSaver ? ES_DISPLAY_REQUIRED : 0) |
        ES_AWAYMODE_REQUIRED;
    SetThreadExecutionState(ES_CONTINUOUS);
    SetThreadExecutionState(state);
}

INT_PTR CALLBACK MainDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        OutputDebugLog(TEXT("WM_INITDIALOG(wParam: %ld, lParam: %ld)"), wParam, lParam);

        // Adjust process info to block shutdown.
        // スタンバイ等を抑制するためにプロセスの情報をあれこれ変更
        SetupPowerOffBlock(true, true);

        // Vista or later: You can display the reason of block (optional)
        // Vista 以降: シャットダウンできない理由を表示できます
        //ShutdownBlockReasonCreate(hDlg, TEXT("Hey! You should work more harder! ;)"));
        return (INT_PTR)TRUE;

    case WM_QUERYENDSESSION:
        OutputDebugLog(TEXT("WM_QUERYENDSESSION(wParam: %ld, lParam: %ld)"), wParam, lParam);

        // Prevent shutdown. Return FALSE as a return value for WM_QUERYENDSESSION.
        // If your window is not a dialog, you can simply use `return FALSE`.
        // シャットダウンを抑制するために FALSE を返します。
        // 通常のウィンドウなら `return FALSE` で構いません。
        SetWindowLong(hDlg, DWL_MSGRESULT, (LPARAM)FALSE);
        return (INT_PTR)TRUE;

    case WM_ENDSESSION:
        OutputDebugLog(TEXT("WM_ENDSESSION(wParam: %ld, lParam: %ld)"), wParam, lParam);

        // Do nothing.
        break;

    // Following handlers are trivial.
    // 以下、本質とは無関係なメッセージ処理

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            DestroyWindow(hDlg);
            return (INT_PTR)TRUE;
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }
    return (INT_PTR)FALSE;
}

// Simple OutputDebugString wrapper which supports formatting and timestamp (datetime).
void OutputDebugLog(LPCTSTR format, ...)
{
    va_list args;
    TCHAR message[4096];
    TCHAR header[64];

    va_start(args, format);
    wvsprintf(message, format, args);
    va_end(args);

    SYSTEMTIME st;
    GetLocalTime(&st);
    wsprintf(header, TEXT("%04d/%02d/%02d %02d:%02d:%02d: "),
        st.wYear, st.wMonth, st.wDay,
        st.wHour, st.wMinute, st.wSecond);

    OutputDebugString(header);
    OutputDebugString(message);
    OutputDebugString(TEXT("\n"));
}