// A Windows Job Object configured to KILL every process in it the instant the // job's last handle closes. This process holds that handle for the whole // session, so when it dies -- gracefully, on Ctrl-C, or on a hard // TerminateProcess by TeslaLauncher -- DOSBox and the render bridge die too. // Kernel-enforced; no cooperative shutdown required (the hung-DOSBox case). using System; using System.Runtime.InteropServices; namespace VwePod { internal sealed class JobObject : IDisposable { private IntPtr _handle; private bool _disposed; public JobObject() { _handle = CreateJobObject(IntPtr.Zero, null); if (_handle == IntPtr.Zero) throw new InvalidOperationException("CreateJobObject failed: " + Marshal.GetLastWin32Error()); var ext = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION { BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION { LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE } }; int len = Marshal.SizeOf(ext); IntPtr p = Marshal.AllocHGlobal(len); try { Marshal.StructureToPtr(ext, p, false); if (!SetInformationJobObject(_handle, JobObjectExtendedLimitInformation, p, (uint)len)) throw new InvalidOperationException("SetInformationJobObject failed: " + Marshal.GetLastWin32Error()); } finally { Marshal.FreeHGlobal(p); } } // Assign a process to the job. Any process the job member later spawns is // automatically in the job too, so a whole tree is captured. public void Add(IntPtr processHandle) { if (!AssignProcessToJobObject(_handle, processHandle)) throw new InvalidOperationException("AssignProcessToJobObject failed: " + Marshal.GetLastWin32Error()); } public void Dispose() { if (_disposed) return; _disposed = true; if (_handle != IntPtr.Zero) { CloseHandle(_handle); // last handle -> KILL_ON_JOB_CLOSE fires _handle = IntPtr.Zero; } } // ---- native ---- private const int JobObjectExtendedLimitInformation = 9; private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000; [StructLayout(LayoutKind.Sequential)] private struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public long PerProcessUserTimeLimit; public long PerJobUserTimeLimit; public uint LimitFlags; public UIntPtr MinimumWorkingSetSize; public UIntPtr MaximumWorkingSetSize; public uint ActiveProcessLimit; public UIntPtr Affinity; public uint PriorityClass; public uint SchedulingClass; } [StructLayout(LayoutKind.Sequential)] private struct IO_COUNTERS { public ulong ReadOperationCount, WriteOperationCount, OtherOperationCount; public ulong ReadTransferCount, WriteTransferCount, OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public UIntPtr ProcessMemoryLimit; public UIntPtr JobMemoryLimit; public UIntPtr PeakProcessMemoryUsed; public UIntPtr PeakJobMemoryUsed; } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetInformationJobObject(IntPtr hJob, int infoClass, IntPtr lpInfo, uint cbInfoLength); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); } }