为了解决这个问题,我必须在win32文件句柄上编写自己的基本流实现。这并不十分困难,因为我不需要实现异步支持、缓冲或查找。
不幸的是,需要使用不安全的代码,但对于将在本地完全信任的情况下运行的控制台应用程序来说,这通常不是问题。
核心流程如下:
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 & FileAccess.Read) != 0; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return (_access & 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 < 0 || count < 0 || (offset + count) < 0
|| (offset + count) > 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 = &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 = &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;
}
}
}
BufferedStream
如果需要,可以围绕它进行缓冲,但是对于控制台输出,
TextWriter
无论如何都将进行字符级缓冲,并且只在换行时刷新。
河流泛滥
Win32Exception
提取错误消息,而不是调用
FormatMessage
本身。
在这个流的基础上,我能够为控制台I/O编写一个简单的包装器:
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;
}
}
}
最后一个结果是,在管道的另一端终止连接后,写入控制台输出会导致一个很好的异常消息:
管道正在关闭
通过捕捉和忽略
IOException
在最外面的一层,我看起来很好去。