Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VPin Process to ChildProcessTracker #11 #12

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
140 changes: 140 additions & 0 deletions VPinballX.starter/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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");

/// <summary>
/// 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.</summary>
/// <remarks>References:
/// https://stackoverflow.com/a/4657392/386091
/// https://stackoverflow.com/a/9164742/386091 </remarks>
public static class ChildProcessTracker
{
/// <summary>
/// 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.</summary>
/// <param name="process"></param>
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;
Expand Down Expand Up @@ -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();
}
Expand Down
12 changes: 4 additions & 8 deletions VPinballX.starter/VPinballX.starter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
<ApplicationIcon>VPinballX.starter.ico</ApplicationIcon>
<Title>VPinballX version starter using an inifile</Title>
<Product>VPinballX.starter</Product>
<AssemblyVersion>1.5</AssemblyVersion>
<Version>1.5</Version>
<FileVersion>1.5</FileVersion>
<AssemblyVersion>1.6</AssemblyVersion>
<Version>1.6</Version>
<FileVersion>1.6</FileVersion>
<Copyright>©COPYRIGHTYEAR Richard Ludwig</Copyright>
<Description>VPinballX.exe version starter using an inifile</Description>
<IsTrimmable>false</IsTrimmable>
<PublishTrimmed>false</PublishTrimmed>
<Configurations>Debug;Release;Release and Deploy</Configurations>
<Configurations>Debug;Release</Configurations>
<StartupObject>VPinballX.starter.App</StartupObject>
<RepositoryUrl>https://github.com/JockeJarre/VPinballX.starter</RepositoryUrl>
</PropertyGroup>
Expand All @@ -25,10 +25,6 @@
<Optimize>False</Optimize>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release and Deploy|AnyCPU'">
<Optimize>True</Optimize>
</PropertyGroup>

<ItemGroup>
<Content Include="VPinballX.starter.ico" />
</ItemGroup>
Expand Down