using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using IPA.Logging; namespace IPA.Utilities { /// /// Provides utilities for managing various critical sections. /// public static class CriticalSection { internal static void Configure() { Logger.Default.Debug("Configuring exit handlers"); ResetExitHandlers(); } private static void Reset(object sender, EventArgs e) { Win32.SetConsoleCtrlHandler(registeredHandler, false); WinHttp.SetPeekMessageHook(null); } #region Execute section private static readonly Win32.ConsoleCtrlDelegate registeredHandler = HandleExit; internal static void ResetExitHandlers() { Win32.SetConsoleCtrlHandler(registeredHandler, false); Win32.SetConsoleCtrlHandler(registeredHandler, true); WinHttp.SetPeekMessageHook(PeekMessageHook); AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; AppDomain.CurrentDomain.ProcessExit += OnProcessExit; } private static void OnProcessExit(object sender, EventArgs args) { WinHttp.SetIgnoreUnhandledExceptions(true); } private static class WinHttp { public delegate bool PeekMessageHook( bool isW, uint result, [MarshalAs(UnmanagedType.LPStruct)] in Win32.MSG message, IntPtr hwnd, uint filterMin, uint filterMax, ref Win32.PeekMessageParams removeMsg); [DllImport("bsipa-doorstop")] public static extern void SetPeekMessageHook( [MarshalAs(UnmanagedType.FunctionPtr)] PeekMessageHook hook); [DllImport("bsipa-doorstop")] public static extern void SetIgnoreUnhandledExceptions( [MarshalAs(UnmanagedType.Bool)] bool ignore); } private static Win32.ConsoleCtrlDelegate _handler = null; private static volatile bool isInExecuteSection = false; // returns true to continue looping and calling PeekMessage private static bool PeekMessageHook( bool isW, uint result, [MarshalAs(UnmanagedType.LPStruct)] in Win32.MSG message, IntPtr hwnd, uint filterMin, uint filterMax, ref Win32.PeekMessageParams removeMsg) { if (isInExecuteSection) { if (result == 0) return false; switch (message.message) { case Win32.WM.CLOSE: if (removeMsg != Win32.PeekMessageParams.PM_REMOVE) { removeMsg = Win32.PeekMessageParams.PM_REMOVE; exitRecieved = true; return true; } else { removeMsg = Win32.PeekMessageParams.PM_NOREMOVE; return true; } default: return false; } } return false; } private static bool HandleExit(Win32.CtrlTypes type) { if (_handler != null) return _handler(type); return false; } private static volatile bool exitRecieved = false; /// /// A struct that allows using blocks to manage an execute section. /// public struct AutoExecuteSection : IDisposable { private readonly bool constructed; internal AutoExecuteSection(bool val) { constructed = val && !isInExecuteSection; if (constructed) EnterExecuteSection(); } void IDisposable.Dispose() { if (constructed) ExitExecuteSection(); } } /// /// Creates an for automated management of an execute section. /// /// the new that manages the section public static AutoExecuteSection ExecuteSection() => new AutoExecuteSection(true); /// /// Enters a critical execution section. Does not nest. /// /// /// During a critical execution section, the program must execute until the end of the section before /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process /// will terminate at the end of the section. /// public static void EnterExecuteSection() { ResetExitHandlers(); exitRecieved = false; _handler = sig => exitRecieved = true; isInExecuteSection = true; } /// /// Exits a critical execution section. Does not nest. /// /// /// During a critical execution section, the program must execute until the end of the section before /// exiting. If an exit signal is recieved during the section, it will be canceled, and the process /// will terminate at the end of the section. /// public static void ExitExecuteSection() { _handler = null; isInExecuteSection = false; Reset(null, null); if (exitRecieved) Environment.Exit(1); } #endregion } }