Skip to content

Console: low-level APIs for getting STD IN/OUT/ERR handles #122802

@adamsitnik

Description

@adamsitnik

Background and motivation

When .NET is starting a new process and not redirecting standard input, output, or error, it uses the handles of the parent process.

As of today, we don't expose any API to get those handles, which makes it impossible to implement advanced scenarios like piping between processes without resorting to P/Invoke.

API Proposal

namespace System;

public static class Console
{
    public static Stream OpenStandardInput();
    public static Stream OpenStandardOutput();
    public static Stream OpenStandardError();
+   public static SafeFileHandle OpenStandardInputHandle();
+   public static SafeFileHandle OpenStandardOutputHandle();
+   public static SafeFileHandle OpenStandardErrorHandle();
}

API Usage

using SafeFileHandle inputHandle = Console.OpenStandardInputHandle();
using SafeFileHandle outputHandle = Console.OpenStandardOutputHandle();
using SafeFileHandle errorHandle = Console.OpenStandardErrorHandle();

using var procHandle = SafeChildProcessHandle.Start(options, inputHandle, outputHandle, errorHandle);
procHandle.WaitForExit();

Alternative Designs

Console already defines methods like Console.OpenStandardInput(), that is why I've used the Open prefix here as well. But we don't really open anything here, we just get existing handles. So another option would be to use the Get prefix instead:

public static SafeFileHandle GetStandardInputHandle();
public static SafeFileHandle GetStandardOutputHandle();
public static SafeFileHandle GetStandardErrorHandle();

And this is exactly how Windows named their sys-call: GetStdHandle.

Risks

SafeFileHandle implements IDisposable, so users typically use using statements to ensure proper cleanup. However, closing STD IN/OUT/ERROR handles of the current process is in 99.99% cases not desired. Therefore, we need to ensure that the returned handles are not closed when the user calls Dispose(). Or at least not by default. So the implementation of the above methods uses the following constructor:

public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle)

With ownsHandle set to false, so that disposing the handle does not close the underlying OS handle.

We could allow the users to specify if they want to "own" the handle. And make it false by default:

public static SafeFileHandle OpenStandardInputHandle(bool ownsHandle = false);
public static SafeFileHandle OpenStandardOutputHandle(bool ownsHandle = false);
public static SafeFileHandle OpenStandardErrorHandle(bool ownsHandle = false);

But if we don't, the 0.01% users can just do the following if they really want to close STD IN/OUT/ERR:

SafeFileHandle inputHandle = new(Console.OpenStandardInputHandle().DangerousGetHandle(), ownsHandle: true);
inputHandle.Dispose();

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions