feat(launcher): prepare Campaign LA11 user gate
This commit is contained in:
parent
09d84387a8
commit
f881e5b467
24 changed files with 3538 additions and 58 deletions
843
src/AcDream.Launcher.Core/Launching/WindowsSystemChildProcess.cs
Normal file
843
src/AcDream.Launcher.Core/Launching/WindowsSystemChildProcess.cs
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace AcDream.Launcher.Core.Launching;
|
||||
|
||||
/// <summary>
|
||||
/// Windows launcher child created without a shell as a true console process-
|
||||
/// group leader. The native start is deliberately narrow: it exists only
|
||||
/// because <see cref="ProcessStartInfo"/> does not expose
|
||||
/// CREATE_NEW_PROCESS_GROUP while the launcher must retain redirected stdin.
|
||||
/// </summary>
|
||||
internal sealed class WindowsSystemChildProcess : ILauncherChildProcess
|
||||
{
|
||||
private readonly LauncherProcessSpec _spec;
|
||||
private readonly IWindowsConsoleControl _consoleControl;
|
||||
private Process? _process;
|
||||
private TextWriter? _standardInput;
|
||||
private int _processGroupId;
|
||||
private bool _raisingEnabled;
|
||||
|
||||
internal WindowsSystemChildProcess(
|
||||
LauncherProcessSpec spec,
|
||||
IWindowsConsoleControl? consoleControl = null)
|
||||
{
|
||||
_spec = spec ?? throw new ArgumentNullException(nameof(spec));
|
||||
_consoleControl = consoleControl ?? WindowsConsoleControl.Instance;
|
||||
}
|
||||
|
||||
public bool HasExited => RequireProcess().HasExited;
|
||||
|
||||
public int ExitCode => RequireProcess().ExitCode;
|
||||
|
||||
public TextWriter StandardInput => _standardInput
|
||||
?? throw new InvalidOperationException("The child process has not started.");
|
||||
|
||||
public event EventHandler? Exited;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_process is not null)
|
||||
{
|
||||
throw new InvalidOperationException("The child process already started.");
|
||||
}
|
||||
|
||||
WindowsProcessStartResult started = WindowsProcessNative.Start(_spec);
|
||||
try
|
||||
{
|
||||
_process = Process.GetProcessById(started.ProcessId);
|
||||
_process.EnableRaisingEvents = true;
|
||||
_process.Exited += OnExited;
|
||||
_raisingEnabled = true;
|
||||
_standardInput = started.TakeStandardInput();
|
||||
_processGroupId = started.ProcessId;
|
||||
started.Resume();
|
||||
}
|
||||
catch
|
||||
{
|
||||
started.Terminate();
|
||||
_standardInput?.Dispose();
|
||||
_standardInput = null;
|
||||
if (_process is not null)
|
||||
{
|
||||
if (_raisingEnabled)
|
||||
{
|
||||
_process.Exited -= OnExited;
|
||||
}
|
||||
|
||||
_process.Dispose();
|
||||
_process = null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
started.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryRequestGracefulStop()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_spec.SupportsConsoleGracefulStop
|
||||
|| _process is not { HasExited: false } process
|
||||
|| _processGroupId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _consoleControl.TrySendBreak(process.Id, _processGroupId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The process may have exited between the state check and the
|
||||
// control request. Graceful-stop attempts never escape Stop().
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CloseMainWindow() => RequireProcess().CloseMainWindow();
|
||||
|
||||
public void Kill() => RequireProcess().Kill(entireProcessTree: true);
|
||||
|
||||
public bool WaitForExit(TimeSpan timeout) => RequireProcess().WaitForExit(timeout);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_standardInput?.Dispose();
|
||||
_standardInput = null;
|
||||
if (_process is not null)
|
||||
{
|
||||
if (_raisingEnabled)
|
||||
{
|
||||
_process.Exited -= OnExited;
|
||||
}
|
||||
|
||||
_process.Dispose();
|
||||
_process = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Process RequireProcess() => _process
|
||||
?? throw new InvalidOperationException("The child process has not started.");
|
||||
|
||||
private void OnExited(object? sender, EventArgs e) =>
|
||||
Exited?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal interface IWindowsConsoleControl
|
||||
{
|
||||
bool TrySendBreak(int childProcessId, int childProcessGroupId);
|
||||
}
|
||||
|
||||
internal sealed class WindowsConsoleControl : IWindowsConsoleControl
|
||||
{
|
||||
private const uint CtrlBreakEvent = 1;
|
||||
|
||||
internal static WindowsConsoleControl Instance { get; } = new();
|
||||
|
||||
private WindowsConsoleControl()
|
||||
{
|
||||
}
|
||||
|
||||
public bool TrySendBreak(int childProcessId, int childProcessGroupId)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows()
|
||||
|| childProcessId <= 0
|
||||
|| childProcessGroupId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (WindowsConsoleSynchronization.Gate)
|
||||
{
|
||||
bool attachedHere = false;
|
||||
try
|
||||
{
|
||||
uint[] processes = new uint[1];
|
||||
if (Native.GetConsoleProcessList(processes, 1) == 0)
|
||||
{
|
||||
if (!Native.AttachConsole((uint)childProcessId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
attachedHere = true;
|
||||
}
|
||||
|
||||
return Native.GenerateConsoleCtrlEvent(
|
||||
CtrlBreakEvent,
|
||||
(uint)childProcessGroupId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (attachedHere)
|
||||
{
|
||||
_ = Native.FreeConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Native
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AttachConsole(uint processId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GenerateConsoleCtrlEvent(
|
||||
uint controlEvent,
|
||||
uint processGroupId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
internal static extern uint GetConsoleProcessList(
|
||||
[Out] uint[] processList,
|
||||
uint processCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A process can be attached to only one console. Child creation and targeted
|
||||
/// control-event attachment therefore share one process-wide gate.
|
||||
/// </summary>
|
||||
internal static class WindowsConsoleSynchronization
|
||||
{
|
||||
internal static object Gate { get; } = new();
|
||||
}
|
||||
|
||||
internal sealed class WindowsProcessStartResult : IDisposable
|
||||
{
|
||||
private readonly SafeKernelHandle _processHandle;
|
||||
private readonly SafeKernelHandle _threadHandle;
|
||||
private SafeFileHandle? _standardInput;
|
||||
private bool _resumed;
|
||||
|
||||
internal WindowsProcessStartResult(
|
||||
int processId,
|
||||
SafeKernelHandle processHandle,
|
||||
SafeKernelHandle threadHandle,
|
||||
SafeFileHandle standardInput)
|
||||
{
|
||||
ProcessId = processId;
|
||||
_processHandle = processHandle;
|
||||
_threadHandle = threadHandle;
|
||||
_standardInput = standardInput;
|
||||
}
|
||||
|
||||
internal int ProcessId { get; }
|
||||
|
||||
internal TextWriter TakeStandardInput()
|
||||
{
|
||||
SafeFileHandle handle = _standardInput
|
||||
?? throw new InvalidOperationException("Standard input was already claimed.");
|
||||
var stream = new FileStream(handle, FileAccess.Write, 4096, isAsync: false);
|
||||
_standardInput = null;
|
||||
try
|
||||
{
|
||||
return new StreamWriter(
|
||||
stream,
|
||||
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
|
||||
{
|
||||
AutoFlush = true,
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Resume()
|
||||
{
|
||||
if (WindowsProcessNative.ResumeThread(_threadHandle) == uint.MaxValue)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"The Windows launcher child could not be resumed.");
|
||||
}
|
||||
|
||||
_resumed = true;
|
||||
}
|
||||
|
||||
internal void Terminate()
|
||||
{
|
||||
if (!_processHandle.IsInvalid)
|
||||
{
|
||||
_ = WindowsProcessNative.TerminateProcess(_processHandle, 74);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_resumed)
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
|
||||
_standardInput?.Dispose();
|
||||
_threadHandle.Dispose();
|
||||
_processHandle.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class WindowsProcessNative
|
||||
{
|
||||
private const uint CreateSuspended = 0x00000004;
|
||||
private const uint CreateNewProcessGroup = 0x00000200;
|
||||
private const uint ExtendedStartupInfoPresent = 0x00080000;
|
||||
private const uint StartfUseStdHandles = 0x00000100;
|
||||
private const short SwHide = 0;
|
||||
private const uint HandleFlagInherit = 0x00000001;
|
||||
private const uint DuplicateSameAccess = 0x00000002;
|
||||
private const uint GenericWrite = 0x40000000;
|
||||
private const uint FileShareRead = 0x00000001;
|
||||
private const uint FileShareWrite = 0x00000002;
|
||||
private const uint OpenExisting = 3;
|
||||
private const uint FileAttributeNormal = 0x00000080;
|
||||
private const int StdOutputHandle = -11;
|
||||
private const int StdErrorHandle = -12;
|
||||
private static readonly IntPtr ProcThreadAttributeHandleList = new(0x00020002);
|
||||
|
||||
internal static WindowsProcessStartResult Start(LauncherProcessSpec spec)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(spec.ExecutablePath);
|
||||
ArgumentNullException.ThrowIfNull(spec.Arguments);
|
||||
|
||||
lock (WindowsConsoleSynchronization.Gate)
|
||||
{
|
||||
bool allocatedConsole = false;
|
||||
try
|
||||
{
|
||||
// An Avalonia launcher started from Explorer has no console.
|
||||
// CREATE_NEW_PROCESS_GROUP alone does not allocate one, and a
|
||||
// console-less group cannot receive GenerateConsoleCtrlEvent.
|
||||
// Allocate one only for the creation transaction, hide it,
|
||||
// let the group leader inherit it, then detach the launcher.
|
||||
// Each such child consequently owns a distinct console as
|
||||
// well as a distinct process group.
|
||||
if (!HasConsole())
|
||||
{
|
||||
if (!AllocConsole())
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"The Windows launcher could not allocate the child console.");
|
||||
}
|
||||
|
||||
allocatedConsole = true;
|
||||
IntPtr consoleWindow = GetConsoleWindow();
|
||||
if (consoleWindow != IntPtr.Zero)
|
||||
{
|
||||
_ = ShowWindow(consoleWindow, SwHide);
|
||||
}
|
||||
}
|
||||
|
||||
return StartCore(spec);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (allocatedConsole)
|
||||
{
|
||||
_ = FreeConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static WindowsProcessStartResult StartCore(LauncherProcessSpec spec)
|
||||
{
|
||||
SafeFileHandle? parentInput = null;
|
||||
try
|
||||
{
|
||||
using SafeFileHandle childInput = CreateChildInputPipe(
|
||||
out SafeFileHandle createdParentInput);
|
||||
parentInput = createdParentInput;
|
||||
using SafeKernelHandle childOutput = DuplicateOrOpenNull(StdOutputHandle);
|
||||
using SafeKernelHandle childError = DuplicateOrOpenNull(StdErrorHandle);
|
||||
using var attributes = new ProcessThreadAttributeList(
|
||||
childInput.DangerousGetHandle(),
|
||||
childOutput.DangerousGetHandle(),
|
||||
childError.DangerousGetHandle());
|
||||
|
||||
var startup = new StartupInfoEx
|
||||
{
|
||||
StartupInfo = new StartupInfo
|
||||
{
|
||||
Size = Marshal.SizeOf<StartupInfoEx>(),
|
||||
Flags = StartfUseStdHandles,
|
||||
StandardInput = childInput.DangerousGetHandle(),
|
||||
StandardOutput = childOutput.DangerousGetHandle(),
|
||||
StandardError = childError.DangerousGetHandle(),
|
||||
},
|
||||
AttributeList = attributes.Pointer,
|
||||
};
|
||||
string executable = ResolveExecutable(spec.ExecutablePath);
|
||||
string commandLineText = BuildCommandLine(executable, spec.Arguments);
|
||||
var commandLine = new StringBuilder(commandLineText, commandLineText.Length + 1);
|
||||
string? workingDirectory = string.IsNullOrWhiteSpace(spec.WorkingDirectory)
|
||||
? null
|
||||
: Path.GetFullPath(spec.WorkingDirectory);
|
||||
|
||||
if (!CreateProcessW(
|
||||
executable,
|
||||
commandLine,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
inheritHandles: true,
|
||||
CreateSuspended | CreateNewProcessGroup | ExtendedStartupInfoPresent,
|
||||
IntPtr.Zero,
|
||||
workingDirectory,
|
||||
ref startup,
|
||||
out ProcessInformation information))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"The Windows launcher child could not be created.");
|
||||
}
|
||||
|
||||
var processHandle = new SafeKernelHandle(
|
||||
information.Process,
|
||||
ownsHandle: true);
|
||||
var threadHandle = new SafeKernelHandle(
|
||||
information.Thread,
|
||||
ownsHandle: true);
|
||||
try
|
||||
{
|
||||
var result = new WindowsProcessStartResult(
|
||||
checked((int)information.ProcessId),
|
||||
processHandle,
|
||||
threadHandle,
|
||||
parentInput);
|
||||
parentInput = null;
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_ = TerminateProcess(processHandle, 74);
|
||||
threadHandle.Dispose();
|
||||
processHandle.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
parentInput?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasConsole()
|
||||
{
|
||||
uint[] processes = new uint[1];
|
||||
return GetConsoleProcessList(processes, 1) != 0;
|
||||
}
|
||||
|
||||
internal static uint ResumeThread(SafeKernelHandle thread) =>
|
||||
NativeResumeThread(thread);
|
||||
|
||||
internal static bool TerminateProcess(SafeKernelHandle process, uint exitCode) =>
|
||||
NativeTerminateProcess(process, exitCode);
|
||||
|
||||
internal static string BuildCommandLine(
|
||||
string executable,
|
||||
IReadOnlyList<string> arguments)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
AppendQuotedArgument(builder, executable);
|
||||
foreach (string argument in arguments)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(argument);
|
||||
builder.Append(' ');
|
||||
AppendQuotedArgument(builder, argument);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void AppendQuotedArgument(StringBuilder builder, string value)
|
||||
{
|
||||
builder.Append('"');
|
||||
int backslashes = 0;
|
||||
foreach (char character in value)
|
||||
{
|
||||
if (character == '\\')
|
||||
{
|
||||
backslashes++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (character == '"')
|
||||
{
|
||||
builder.Append('\\', backslashes * 2 + 1);
|
||||
builder.Append('"');
|
||||
backslashes = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Append('\\', backslashes);
|
||||
backslashes = 0;
|
||||
builder.Append(character);
|
||||
}
|
||||
|
||||
builder.Append('\\', backslashes * 2);
|
||||
builder.Append('"');
|
||||
}
|
||||
|
||||
private static SafeFileHandle CreateChildInputPipe(out SafeFileHandle parentInput)
|
||||
{
|
||||
var security = new SecurityAttributes
|
||||
{
|
||||
Length = Marshal.SizeOf<SecurityAttributes>(),
|
||||
InheritHandle = true,
|
||||
};
|
||||
if (!CreatePipe(out IntPtr read, out IntPtr write, ref security, 0))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"The launcher child stdin pipe could not be created.");
|
||||
}
|
||||
|
||||
var child = new SafeFileHandle(read, ownsHandle: true);
|
||||
parentInput = new SafeFileHandle(write, ownsHandle: true);
|
||||
if (!SetHandleInformation(
|
||||
parentInput,
|
||||
HandleFlagInherit,
|
||||
0))
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
child.Dispose();
|
||||
parentInput.Dispose();
|
||||
throw new Win32Exception(error,
|
||||
"The launcher child stdin pipe could not be isolated.");
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
private static SafeKernelHandle DuplicateOrOpenNull(int standardHandle)
|
||||
{
|
||||
IntPtr source = GetStdHandle(standardHandle);
|
||||
if (source != IntPtr.Zero && source != new IntPtr(-1))
|
||||
{
|
||||
IntPtr current = GetCurrentProcess();
|
||||
if (DuplicateHandle(
|
||||
current,
|
||||
source,
|
||||
current,
|
||||
out IntPtr duplicate,
|
||||
0,
|
||||
inheritHandle: true,
|
||||
DuplicateSameAccess))
|
||||
{
|
||||
return new SafeKernelHandle(duplicate, ownsHandle: true);
|
||||
}
|
||||
}
|
||||
|
||||
IntPtr nul = CreateFileW(
|
||||
"NUL",
|
||||
GenericWrite,
|
||||
FileShareRead | FileShareWrite,
|
||||
IntPtr.Zero,
|
||||
OpenExisting,
|
||||
FileAttributeNormal,
|
||||
IntPtr.Zero);
|
||||
if (nul == IntPtr.Zero || nul == new IntPtr(-1))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
"The launcher child fallback output handle could not be opened.");
|
||||
}
|
||||
|
||||
var handle = new SafeKernelHandle(nul, ownsHandle: true);
|
||||
if (!SetHandleInformation(handle, HandleFlagInherit, HandleFlagInherit))
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
handle.Dispose();
|
||||
throw new Win32Exception(error,
|
||||
"The launcher child fallback output handle could not be inherited.");
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private static string ResolveExecutable(string executable)
|
||||
{
|
||||
if (Path.IsPathFullyQualified(executable))
|
||||
{
|
||||
return Path.GetFullPath(executable);
|
||||
}
|
||||
|
||||
var buffer = new StringBuilder(32_768);
|
||||
uint length = SearchPathW(
|
||||
null,
|
||||
executable,
|
||||
null,
|
||||
(uint)buffer.Capacity,
|
||||
buffer,
|
||||
IntPtr.Zero);
|
||||
if (length == 0 || length >= buffer.Capacity)
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error(),
|
||||
$"Launcher child executable '{executable}' was not found.");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(buffer.ToString());
|
||||
}
|
||||
|
||||
private sealed class ProcessThreadAttributeList : IDisposable
|
||||
{
|
||||
private IntPtr _pointer;
|
||||
private IntPtr _handles;
|
||||
private bool _initialized;
|
||||
|
||||
internal ProcessThreadAttributeList(params IntPtr[] handles)
|
||||
{
|
||||
nuint size = 0;
|
||||
_ = InitializeProcThreadAttributeList(
|
||||
IntPtr.Zero,
|
||||
1,
|
||||
0,
|
||||
ref size);
|
||||
_pointer = Marshal.AllocHGlobal(checked((nint)size));
|
||||
if (!InitializeProcThreadAttributeList(_pointer, 1, 0, ref size))
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
Dispose();
|
||||
throw new Win32Exception(error,
|
||||
"The launcher child handle list could not be initialized.");
|
||||
}
|
||||
_initialized = true;
|
||||
|
||||
_handles = Marshal.AllocHGlobal(handles.Length * IntPtr.Size);
|
||||
for (int index = 0; index < handles.Length; index++)
|
||||
{
|
||||
Marshal.WriteIntPtr(_handles, index * IntPtr.Size, handles[index]);
|
||||
}
|
||||
|
||||
if (!UpdateProcThreadAttribute(
|
||||
_pointer,
|
||||
0,
|
||||
ProcThreadAttributeHandleList,
|
||||
_handles,
|
||||
checked((nuint)(handles.Length * IntPtr.Size)),
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero))
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
Dispose();
|
||||
throw new Win32Exception(error,
|
||||
"The launcher child inherited-handle list could not be set.");
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr Pointer => _pointer;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_pointer != IntPtr.Zero)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
DeleteProcThreadAttributeList(_pointer);
|
||||
_initialized = false;
|
||||
}
|
||||
Marshal.FreeHGlobal(_pointer);
|
||||
_pointer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_handles != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(_handles);
|
||||
_handles = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SecurityAttributes
|
||||
{
|
||||
internal int Length;
|
||||
internal IntPtr SecurityDescriptor;
|
||||
[MarshalAs(UnmanagedType.Bool)] internal bool InheritHandle;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct StartupInfo
|
||||
{
|
||||
internal int Size;
|
||||
internal string? Reserved;
|
||||
internal string? Desktop;
|
||||
internal string? Title;
|
||||
internal int X;
|
||||
internal int Y;
|
||||
internal int XSize;
|
||||
internal int YSize;
|
||||
internal int XCountChars;
|
||||
internal int YCountChars;
|
||||
internal int FillAttribute;
|
||||
internal uint Flags;
|
||||
internal short ShowWindow;
|
||||
internal short Reserved2Size;
|
||||
internal IntPtr Reserved2;
|
||||
internal IntPtr StandardInput;
|
||||
internal IntPtr StandardOutput;
|
||||
internal IntPtr StandardError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct StartupInfoEx
|
||||
{
|
||||
internal StartupInfo StartupInfo;
|
||||
internal IntPtr AttributeList;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ProcessInformation
|
||||
{
|
||||
internal IntPtr Process;
|
||||
internal IntPtr Thread;
|
||||
internal uint ProcessId;
|
||||
internal uint ThreadId;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CreateProcessW(
|
||||
string applicationName,
|
||||
StringBuilder commandLine,
|
||||
IntPtr processAttributes,
|
||||
IntPtr threadAttributes,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool inheritHandles,
|
||||
uint creationFlags,
|
||||
IntPtr environment,
|
||||
string? currentDirectory,
|
||||
ref StartupInfoEx startupInfo,
|
||||
out ProcessInformation processInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CreatePipe(
|
||||
out IntPtr readPipe,
|
||||
out IntPtr writePipe,
|
||||
ref SecurityAttributes pipeAttributes,
|
||||
uint size);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetHandleInformation(
|
||||
SafeHandle handle,
|
||||
uint mask,
|
||||
uint flags);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint GetConsoleProcessList(
|
||||
[Out] uint[] processList,
|
||||
uint processCount);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetConsoleWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool ShowWindow(IntPtr window, int commandShow);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr GetStdHandle(int standardHandle);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DuplicateHandle(
|
||||
IntPtr sourceProcess,
|
||||
IntPtr sourceHandle,
|
||||
IntPtr targetProcess,
|
||||
out IntPtr targetHandle,
|
||||
uint desiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool inheritHandle,
|
||||
uint options);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern IntPtr CreateFileW(
|
||||
string fileName,
|
||||
uint desiredAccess,
|
||||
uint shareMode,
|
||||
IntPtr securityAttributes,
|
||||
uint creationDisposition,
|
||||
uint flagsAndAttributes,
|
||||
IntPtr templateFile);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern uint SearchPathW(
|
||||
string? path,
|
||||
string fileName,
|
||||
string? extension,
|
||||
uint bufferLength,
|
||||
StringBuilder buffer,
|
||||
IntPtr filePart);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool InitializeProcThreadAttributeList(
|
||||
IntPtr attributeList,
|
||||
int attributeCount,
|
||||
int flags,
|
||||
ref nuint size);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool UpdateProcThreadAttribute(
|
||||
IntPtr attributeList,
|
||||
uint flags,
|
||||
IntPtr attribute,
|
||||
IntPtr value,
|
||||
nuint size,
|
||||
IntPtr previousValue,
|
||||
IntPtr returnSize);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern void DeleteProcThreadAttributeList(IntPtr attributeList);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "ResumeThread", SetLastError = true)]
|
||||
private static extern uint NativeResumeThread(SafeKernelHandle thread);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "TerminateProcess", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool NativeTerminateProcess(
|
||||
SafeKernelHandle process,
|
||||
uint exitCode);
|
||||
}
|
||||
|
||||
internal sealed class SafeKernelHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
internal SafeKernelHandle(IntPtr handle, bool ownsHandle)
|
||||
: base(ownsHandle)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle() => CloseHandle(handle);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr handle);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue