diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 4d5e9f2..ec5ea9c 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -23,7 +23,7 @@ jobs:
echo "sha7=${SHA7}"
echo "sha=${SHA}" >> $GITHUB_OUTPUT
echo "sha7=${SHA7}" >> $GITHUB_OUTPUT
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
ref: ${{ steps.sha.outputs.sha }}
fetch-depth: 0
diff --git a/VPinballX.starter/App.xaml.cs b/VPinballX.starter/App.xaml.cs
index dfa42bc..ad3ebd7 100644
--- a/VPinballX.starter/App.xaml.cs
+++ b/VPinballX.starter/App.xaml.cs
@@ -21,6 +21,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
using OpenMcdf;
using System.Runtime.InteropServices;
using Salaros.Configuration;
+using System.ComponentModel;
+
namespace VPinballX.starter
{
@@ -35,6 +37,141 @@ public partial class App : Application
public static string strIniConfigFilename = "VPinballX.starter.ini";
public static string strLogFilename = Path.Combine(App.strExeFilePath, "VPinballX.starter.log");
+ ///
+ /// Allows processes to be automatically killed if this parent process unexpectedly quits.
+ /// This feature requires Windows 8 or greater. On Windows 7, nothing is done.
+ /// References:
+ /// https://stackoverflow.com/a/4657392/386091
+ /// https://stackoverflow.com/a/9164742/386091
+ public static class ChildProcessTracker
+ {
+ ///
+ /// Add the process to be tracked. If our current process is killed, the child processes
+ /// that we are tracking will be automatically killed, too. If the child process terminates
+ /// first, that's fine, too.
+ ///
+ public static void AddProcess(Process process)
+ {
+ if (s_jobHandle != IntPtr.Zero)
+ {
+ bool success = AssignProcessToJobObject(s_jobHandle, process.Handle);
+ if (!success && !process.HasExited)
+ throw new Win32Exception();
+ }
+ }
+
+ static ChildProcessTracker()
+ {
+ // This feature requires Windows 8 or later. To support Windows 7 requires
+ // registry settings to be added if you are using Visual Studio plus an
+ // app.manifest change.
+ // https://stackoverflow.com/a/4232259/386091
+ // https://stackoverflow.com/a/9507862/386091
+ if (Environment.OSVersion.Version < new Version(6, 2))
+ return;
+
+ // The job name is optional (and can be null) but it helps with diagnostics.
+ // If it's not null, it has to be unique. Use SysInternals' Handle command-line
+ // utility: handle -a ChildProcessTracker
+ string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
+ s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);
+
+ var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
+
+ // This is the key flag. When our process is killed, Windows will automatically
+ // close the job handle, and when that happens, we want the child processes to
+ // be killed, too.
+ info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
+ extendedInfo.BasicLimitInformation = info;
+
+ int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
+ IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
+ try
+ {
+ Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
+
+ if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
+ extendedInfoPtr, (uint)length))
+ {
+ throw new Win32Exception();
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(extendedInfoPtr);
+ }
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);
+
+ [DllImport("kernel32.dll")]
+ static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
+ IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
+
+ // Windows will automatically close any open job handles when our process terminates.
+ // This can be verified by using SysInternals' Handle utility. When the job handle
+ // is closed, the child processes will be killed.
+ private static readonly IntPtr s_jobHandle;
+ }
+
+ public enum JobObjectInfoType
+ {
+ AssociateCompletionPortInformation = 7,
+ BasicLimitInformation = 2,
+ BasicUIRestrictions = 4,
+ EndOfJobTimeInformation = 6,
+ ExtendedLimitInformation = 9,
+ SecurityLimitInformation = 5,
+ GroupInformation = 11
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
+ {
+ public Int64 PerProcessUserTimeLimit;
+ public Int64 PerJobUserTimeLimit;
+ public JOBOBJECTLIMIT LimitFlags;
+ public UIntPtr MinimumWorkingSetSize;
+ public UIntPtr MaximumWorkingSetSize;
+ public UInt32 ActiveProcessLimit;
+ public Int64 Affinity;
+ public UInt32 PriorityClass;
+ public UInt32 SchedulingClass;
+ }
+
+ [Flags]
+ public enum JOBOBJECTLIMIT : uint
+ {
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct IO_COUNTERS
+ {
+ public UInt64 ReadOperationCount;
+ public UInt64 WriteOperationCount;
+ public UInt64 OtherOperationCount;
+ public UInt64 ReadTransferCount;
+ public UInt64 WriteTransferCount;
+ public UInt64 OtherTransferCount;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public 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;
+ }
public static class Native
{
public const int MB_OK = (int)0x00000000L;
@@ -311,9 +448,12 @@ void StartAnotherProgram(string programPath, string[] programArgs)
}
process.StartInfo = startInfo;
process.Start();
+ // Add the Process to ChildProcessTracker.
+ ChildProcessTracker.AddProcess(process);
process.WaitForInputIdle(10000);
+
process.WaitForExit();
process.Close();
}
diff --git a/VPinballX.starter/VPinballX.starter.csproj b/VPinballX.starter/VPinballX.starter.csproj
index ca4884c..75b689f 100644
--- a/VPinballX.starter/VPinballX.starter.csproj
+++ b/VPinballX.starter/VPinballX.starter.csproj
@@ -9,14 +9,14 @@
VPinballX.starter.ico
VPinballX version starter using an inifile
VPinballX.starter
- 1.5
- 1.5
- 1.5
+ 1.6
+ 1.6
+ 1.6
©COPYRIGHTYEAR Richard Ludwig
VPinballX.exe version starter using an inifile
false
false
- Debug;Release;Release and Deploy
+ Debug;Release
VPinballX.starter.App
https://github.com/JockeJarre/VPinballX.starter
@@ -25,10 +25,6 @@
False
-
- True
-
-