Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>To solve this one, I had to write my own basic stream implementation over Win32 file handles. This wasn't terribly difficult, as I didn't need to implement asynchronous support, buffering or seeking.</p> <p>Unfortunately, unsafe code needs to be used, but that generally isn't a problem for console applications that will be run locally and with full trust.</p> <p>Here's the core stream:</p> <pre><code>class HandleStream : Stream { SafeHandle _handle; FileAccess _access; bool _eof; public HandleStream(SafeHandle handle, FileAccess access) { _handle = handle; _access = access; } public override bool CanRead { get { return (_access &amp; FileAccess.Read) != 0; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return (_access &amp; FileAccess.Write) != 0; } } public override void Flush() { // use external buffering if you need it. } public override long Length { get { throw new NotSupportedException(); } } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } static void CheckRange(byte[] buffer, int offset, int count) { if (offset &lt; 0 || count &lt; 0 || (offset + count) &lt; 0 || (offset + count) &gt; buffer.Length) throw new ArgumentOutOfRangeException(); } public bool EndOfStream { get { return _eof; } } public override int Read(byte[] buffer, int offset, int count) { CheckRange(buffer, offset, count); int result = ReadFileNative(_handle, buffer, offset, count); _eof |= result == 0; return result; } public override void Write(byte[] buffer, int offset, int count) { int notUsed; Write(buffer, offset, count, out notUsed); } public void Write(byte[] buffer, int offset, int count, out int written) { CheckRange(buffer, offset, count); int result = WriteFileNative(_handle, buffer, offset, count); _eof |= result == 0; written = result; } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } [return: MarshalAs(UnmanagedType.Bool)] [DllImport("kernel32", SetLastError=true)] static extern unsafe bool ReadFile( SafeHandle hFile, byte* lpBuffer, int nNumberOfBytesToRead, out int lpNumberOfBytesRead, IntPtr lpOverlapped); [return: MarshalAs(UnmanagedType.Bool)] [DllImport("kernel32.dll", SetLastError=true)] static extern unsafe bool WriteFile( SafeHandle hFile, byte* lpBuffer, int nNumberOfBytesToWrite, out int lpNumberOfBytesWritten, IntPtr lpOverlapped); unsafe static int WriteFileNative(SafeHandle hFile, byte[] buffer, int offset, int count) { if (buffer.Length == 0) return 0; fixed (byte* bufAddr = &amp;buffer[0]) { int result; if (!WriteFile(hFile, bufAddr + offset, count, out result, IntPtr.Zero)) { // Using Win32Exception just to get message resource from OS. Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error()); int hr = ex.NativeErrorCode | unchecked((int) 0x80000000); throw new IOException(ex.Message, hr); } return result; } } unsafe static int ReadFileNative(SafeHandle hFile, byte[] buffer, int offset, int count) { if (buffer.Length == 0) return 0; fixed (byte* bufAddr = &amp;buffer[0]) { int result; if (!ReadFile(hFile, bufAddr + offset, count, out result, IntPtr.Zero)) { Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error()); int hr = ex.NativeErrorCode | unchecked((int) 0x80000000); throw new IOException(ex.Message, hr); } return result; } } } </code></pre> <p><code>BufferedStream</code> can be wrapped around it for buffering if needed, but for console output, the <code>TextWriter</code> will be doing character-level buffering anyway, and only flushing on newlines.</p> <p>The stream abuses <code>Win32Exception</code> to extract an error message, rather than calling <code>FormatMessage</code> itself.</p> <p>Building on this stream, I was able to write a simple wrapper for console I/O:</p> <pre><code>static class ConsoleStreams { enum StdHandle { Input = -10, Output = -11, Error = -12, } [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int nStdHandle); static SafeHandle GetStdHandle(StdHandle h) { return new SafeFileHandle(GetStdHandle((int) h), true); } public static HandleStream OpenStandardInput() { return new HandleStream(GetStdHandle(StdHandle.Input), FileAccess.Read); } public static HandleStream OpenStandardOutput() { return new HandleStream(GetStdHandle(StdHandle.Output), FileAccess.Write); } public static HandleStream OpenStandardError() { return new HandleStream(GetStdHandle(StdHandle.Error), FileAccess.Write); } static TextReader _in; static StreamWriter _out; static StreamWriter _error; public static TextWriter Out { get { if (_out == null) { _out = new StreamWriter(OpenStandardOutput()); _out.AutoFlush = true; } return _out; } } public static TextWriter Error { get { if (_error == null) { _error = new StreamWriter(OpenStandardError()); _error.AutoFlush = true; } return _error; } } public static TextReader In { get { if (_in == null) _in = new StreamReader(OpenStandardInput()); return _in; } } } </code></pre> <p>The final result is that writing to the console output after the other end of the pipe has terminated the connection, results in a nice exception with the message:</p> <blockquote> <p>The pipe is being closed</p> </blockquote> <p>By catching and ignoring the <code>IOException</code> at the outermost level, it looks like I'm good to go.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload