Skip to content

Add Process.Start callback overload for advanced Interop #71595

Description

@NN---

Background and motivation

There are plenty of ways to create a process where .NET does not support this or where there is already a process handle from native method.
Currently there is no possibility to create a .NET Process object from the SafeProcessHandle.

API Proposal

namespace System.Runtime.InteropServices;

[SupportedOSPlatform("windows")]
public ref struct WindowsProcessStartArguments
{
    public WindowsProcessStartArguments();

    public unsafe char* Arguments { get; }
    public unsafe char* EnvironmentVariables{ get; }

    public ProcessStartInfo ProcessStartInfo { get; }
    public nint StandardError { get; }
    public nint StandardInput { get; }
    public nint StandardOutput { get; }

    public static Process Start(ProcessStartInfo startInfo, Func<WindowsProcessStartArguments, SafeProcessHandle> callback);
}

[UnsupportedOSPlatform("windows")]
public ref struct UnixProcessStartArguments
{
    public UnixProcessStartArguments();

    public unsafe byte* ResolvedPath { get; }
    public unsafe byte** Arguments { get; }
    public unsafe byte** EnvironmentVariables { get; }

    public ProcessStartInfo ProcessStartInfo { get; }
    public nint StandardError { get; }
    public nint StandardInput { get; }
    public nint StandardOutput { get; }

    [UnsupportedOSPlatform("ios")]
    [UnsupportedOSPlatform("tvos")]
    [UnsupportedOSPlatform("maccatalyst")]
    public static Process Start(ProcessStartInfo startInfo, Func<UnixProcessStartArguments, int> callback);
}

API Usage

ProcessStartInfo startInfo = new("cmd")
{
    ArgumentList = { "/c", "echo hello && echo error 1>&2" },
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

using Process process = WindowsProcessStartArguments.Start(startInfo, (WindowsProcessStartArguments args) =>
{
    Interop.Kernel32.STARTUPINFOEX startupInfoEx = default;
    Interop.Kernel32.PROCESS_INFORMATION processInfo = default;
    Interop.Kernel32.SECURITY_ATTRIBUTES unused_SecAttrs = default;

    startupInfoEx.StartupInfo.cb = sizeof(Interop.Kernel32.STARTUPINFOEX);
    startupInfoEx.StartupInfo.hStdInput = args.StandardInput;
    startupInfoEx.StartupInfo.hStdOutput = args.StandardOutput;
    startupInfoEx.StartupInfo.hStdError = args.StandardError;
    startupInfoEx.StartupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES;

    bool retVal = Interop.Kernel32.CreateProcess(
        null,
        args.Arguments,
        ref unused_SecAttrs,
        ref unused_SecAttrs,
        bInheritHandles: true,
        Interop.Kernel32.EXTENDED_STARTUPINFO_PRESENT,
        args.EnvironmentVariables,
        null,
        &startupInfoEx,
        &processInfo
    );

    if (!retVal)
    {
        throw new Win32Exception();
    }

    Interop.Kernel32.CloseHandle(processInfo.hThread);

    return new SafeProcessHandle(processInfo.hProcess, ownsHandle: true);
});

Alternative Designs

The alternative design included a ctor, but it was too fragile. Check #128491 for details

Risks

Can't see any as of now, as BCL takes care of removing all known risks before invoking the callback.

Working prototype: #128862

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Diagnostics.Processin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions