Skip to content

Instantly share code, notes, and snippets.

@eric-b
Created April 6, 2016 18:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save eric-b/0db6e8a65775de81799d56dcdd46f9ba to your computer and use it in GitHub Desktop.
Save eric-b/0db6e8a65775de81799d56dcdd46f9ba to your computer and use it in GitHub Desktop.
SetConsoleCtrlHandler
using System.Runtime.InteropServices;
namespace ConsoleDemo
{
internal static class NativeMethods
{
// Doc MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686016(v=vs.85).aspx
/// <summary>
/// <para>Adds or removes an <see cref="HandlerRoutine"/> delegate function
/// from the list of handler functions for the calling process.</para>
/// <para>If no delegate function is specified, the function sets an inheritable
/// attribute that determines whether the calling process ignores CTRL+C signals.</para>
/// </summary>
/// <param name="handler"></param>
/// <param name="add"><para>If this parameter is TRUE, the handler is added; if it is FALSE, the handler is removed.</para>
/// <remarks>If the HandlerRoutine parameter is NULL, a TRUE value causes the calling process to ignore CTRL+C input,
/// and a FALSE value restores normal processing of CTRL+C input.
/// This attribute of ignoring or processing CTRL+C is inherited by child processes.</remarks></param>
/// <returns><c>true</c> if the function succeeds.</returns>
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);
/// <summary>
/// <para>A delegate type to be used as the handler routine
/// for <see cref="SetConsoleCtrlHandler"/>. </para>
/// <para>A console process uses this function to handle control signals received by the process.
/// When the signal is received, the system creates a new thread in the process to execute the function.</para>
/// </summary>
/// <param name="ctrlType"></param>
/// <returns>If the function handles the control signal, it should return TRUE.
/// If it returns FALSE, the next handler function in the list of handlers for this process is used.
/// See MSDN remarks for details.</returns>
public delegate bool HandlerRoutine(CtrlTypes ctrlType);
/// <summary>
/// The type of control signal received by <see cref="HandlerRoutine"/>.
/// </summary>
public enum CtrlTypes
{
/// <summary>
/// A CTRL+C signal was received, either from keyboard input or from a signal generated by
/// the GenerateConsoleCtrlEvent function.
/// </summary>
CTRL_C_EVENT = 0,
/// <summary>
/// A CTRL+BREAK signal was received, either from keyboard input or from a signal
/// generated by GenerateConsoleCtrlEvent.
/// </summary>
CTRL_BREAK_EVENT = 1,
/// <summary>
/// A signal that the system sends to all processes attached to a console when the user
/// closes the console (either by clicking Close on the console window's window menu,
/// or by clicking the End Task button command from Task Manager).
/// </summary>
CTRL_CLOSE_EVENT = 2,
/// <summary>
/// <para>A signal that the system sends to all console processes when a user is logging off.
/// This signal does not indicate which user is logging off, so no assumptions can be made.</para>
/// <remarks>Note that this signal is received only by services.
/// Interactive applications are terminated at logoff,
/// so they are not present when the system sends this signal.</remarks>
/// </summary>
CTRL_LOGOFF_EVENT = 5,
/// <summary>
/// <para>A signal that the system sends when the system is shutting down.
/// Interactive applications are not present by the time the system sends this signal,
/// therefore it can be received only be services in this situation.
/// Services also have their own notification mechanism for shutdown events.
/// For more information, see Handler.</para>
/// <remarks>This signal can also be generated by an application using GenerateConsoleCtrlEvent.</remarks>
/// </summary>
CTRL_SHUTDOWN_EVENT = 6
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleDemo
{
sealed class Program : IDisposable
{
private static Program _program;
private static bool _isClosing;
private readonly CancellationTokenSource _cts;
private readonly ManualResetEvent _signal;
private int _disposeCount;
public Program()
{
_cts = new CancellationTokenSource();
_signal = new ManualResetEvent(initialState: false);
}
public void StartTaskAndBlock()
{
Task task = Task.Delay(20000, _cts.Token);
WriteLine("Busy...");
try
{
task.Wait();
}
catch (AggregateException ex)
{
WriteLine(ex.Flatten().ToString());
}
finally
{
// simulate exit latency for a real program
WriteLine("Stopping...");
Thread.Sleep(3000);
_signal.Set();
}
}
public void CancelAndBlock()
{
try
{
_cts.Cancel();
_signal.WaitOne();
((IDisposable)this).Dispose();
}
catch (Exception ex)
{
WriteLine(ex.ToString());
}
}
static void Main(string[] args)
{
try
{
using (_program = new Program())
{
NativeMethods.SetConsoleCtrlHandler(ConsoleCtrlHandler, add: true);
_program.StartTaskAndBlock();
}
}
catch(Exception ex)
{
WriteLine(ex.ToString());
}
}
static bool ConsoleCtrlHandler(NativeMethods.CtrlTypes ctrlType)
{
if (_isClosing)
return false;
_isClosing = true;
const bool signalHandled = true, signalIgnored = false;
if (ctrlType == NativeMethods.CtrlTypes.CTRL_C_EVENT ||
ctrlType == NativeMethods.CtrlTypes.CTRL_CLOSE_EVENT)
{
try
{
_program.CancelAndBlock();
return signalHandled;
}
catch (Exception ex)
{
WriteLine(ex.ToString());
throw;
}
}
return signalIgnored;
}
void IDisposable.Dispose()
{
if (Interlocked.Increment(ref _disposeCount) != 1)
return;
_cts.Dispose();
_signal.Dispose();
WriteLine("Clean exit!");
}
static void WriteLine(string format, params object[] args)
{
string line = string.Format(format, args);
Console.WriteLine(line);
Debug.WriteLine(line);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment