Как использовать SetConsoleHandler() для блокировки вызовов выхода

StackOverflow https://stackoverflow.com/questions/533491

  •  22-08-2019
  •  | 
  •  

Вопрос

Я знаю, что мне придется использовать setconsolehandler() если я хочу управлять событиями закрытия консоли.

Я не знаю, как заблокировать CTRL_CLOSE_EVENT.Я пробовал вернуть false/true, если оно перехватывает это событие, но безуспешно.

Вот что у меня есть на данный момент (спасибо Антону Гоголеву!)

[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);

public delegate bool HandlerRoutine(CtrlTypes CtrlType);

public enum CtrlTypes{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{ 
    if(ctrlType == CtrlTypes.CTRL_CLOSE_EVENT)
        return false;// I have tried true and false and viceversa with the return   
                     // true/false but I cant seem to get it right.
    return true;
}


//and then I use this to call it
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);

Также можно ли запустить новый поток, чтобы отслеживать, закрывается ли консоль, и блокировать это закрытие, если основной поток что-то делает?

Это было полезно?

Решение

Документация для SetConsoleCtrlHandler() говорит:

Система генерирует сигналы CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT и CTRL_SHUTDOWN_EVENT, когда пользователь закрывает консоль, выходит из системы или завершает работу системы, чтобы процесс имел возможность очиститься перед завершением.

Это означает, что в отличие от обработки событий CTRL+C или CTRL+BREAK, ваш процесс не получает возможности отменить закрытие, выход из системы или завершение работы.

Другие советы

На самом деле вы можете заблокировать его (по крайней мере, я воспроизвел это в Windows XP).Например, если в вашем обработчике есть бесконечный цикл while со сном, это предотвратит завершение этого процесса навсегда (или, по крайней мере, на долгое время, или пока пользователь не завершит процесс через диспетчер задач).

Если вам действительно нужно запустить поток, вы можете использовать условие ожидания (AutoResetEvent в C#) и запустите свой поток (хотя в большинстве случаев новый поток, вероятно, не нужен), а затем уведомите условие ожидания, когда ваш поток завершится.Однако в большинстве случаев достаточно просто выполнить очистку в обработчике.

Если в худшем случае вы ждали вечно, процесс продолжит работать, и вы сможете увидеть его при повторном входе в систему (по крайней мере, в Windows XP).Однако это приводит к тому, что рабочий стол приостанавливается примерно на 20 секунд перед переходом к экрану выхода из системы (пока он ждет выхода вашего приложения), а затем снова приостанавливается на экране выхода из системы (я полагаю, пока он пытается во второй раз) .Конечно, я настоятельно не советую ждать вечно;для любых долго работающих вещей вам действительно следует поместить это в сервис.

Я нашел возможный «взлом», который мог бы предотвратить закрытие приложения, при этом аккуратно подчиняясь запросу закрытия консоли.В частности, я думаю, это подходит для приложений с графическим интерфейсом, которые создали консоль в качестве «дополнительного».Из документации MSDN, в тот момент, когда обработчик Ctrl вызывается новый поток создается в процессе вызова обработчика.Мое решение состоит в том, чтобы уничтожить атакующий поток до того, как обработчик по умолчанию сможет вызвать ExitProcess.В подпрограмме-обработчике (код на C++):

// Detach Console:
FreeConsole();
// Prevent closing:
ExitThread(0);
return TRUE; // Not reached

РЕДАКТИРОВАТЬ: Похоже, это действительно вызывает некоторые проблемы, как и следовало ожидать.Последующий вызов AllocConsole() зависает на неопределенный срок, поэтому я подозреваю, что преждевременное завершение потока не приводит к правильной очистке.

РЕДАКТИРОВАТЬ 2:

Чтобы уточнить выше, я не обнаружил прямых проблем с продолжением работы программы.Но имейте в виду, что мы принудительно завершили поток, созданный kernel32, поэтому любые ресурсы внутри kernel32 могут находиться в неопределенном состоянии.Это может вызвать непредвиденные проблемы при продолжении работы программы.

В основном, я думаю, эти проблемы будут связаны с API консоли.Как уже упоминалось, AllocConsole с этого момента не работает (приложение зависает), поэтому программа не может открыть новую консоль.Вполне возможно, что другие функции консоли также не будут работать.По сути, все, что вы делаете с этого момента и каким-либо образом (прямо или косвенно) вызываете kernel32, подвергается неопределенному поведению, но я подозреваю, что на практике не будет никаких проблем за пределами функций консоли.

Мой вывод таков: вам следует избегать этого метода, если это вообще возможно, но если преждевременное прекращение еще хуже, то его можно рассматривать как экстренный обходной путь, который следует использовать экономно и осторожно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top