.NET CORE 怎么样从控制台中读取输入流

  • 2019 年 10 月 3 日
  • 筆記

.NET CORE 怎么样从控制台中读取输入流

从Console.ReadList/Read 的源码中,可学习到.NET CORE 是怎么样来读取输入流。
也可以学习到是如何使用P/Invoke来调用系统API

Console.ReadList 的源码为

        [MethodImplAttribute(MethodImplOptions.NoInlining)]          public static string ReadLine()          {              return In.ReadLine();          }

其中In为。

          internal static T EnsureInitialized<T>(ref T field, Func<T> initializer) where T : class =>              LazyInitializer.EnsureInitialized(ref field, ref InternalSyncObject, initializer);            public static TextReader In => EnsureInitialized(ref s_in, () => ConsolePal.GetOrCreateReader());

可以看到他是个TextRead
接下来,我们看看ConsolePal.GetOrCreateReader()方法中,是怎么样获取到一个Reader的。
转到ConsolePal.Windows.cs 的源码,可以看到,

internal static TextReader GetOrCreateReader()          {              Stream inputStream = OpenStandardInput();              return SyncTextReader.GetSynchronizedTextReader(inputStream == Stream.Null ?                  StreamReader.Null :                  new StreamReader(                      stream: inputStream,                      encoding: new ConsoleEncoding(Console.InputEncoding),                      detectEncodingFromByteOrderMarks: false,                      bufferSize: Console.ReadBufferSize,                      leaveOpen: true));          }

继续跳转,查看方法OpenStandardInput

          public static Stream OpenStandardInput()          {              return GetStandardFile(Interop.Kernel32.HandleTypes.STD_INPUT_HANDLE, FileAccess.Read);          }

继续看方法

          private static Stream GetStandardFile(int handleType, FileAccess access)          {              IntPtr handle = Interop.Kernel32.GetStdHandle(handleType);              // 此处源码一坨注释被我删掉了。^_^              if (handle == IntPtr.Zero || handle == InvalidHandleValue ||                  (access != FileAccess.Read && !ConsoleHandleIsWritable(handle)))              {                  return Stream.Null;              }                return new WindowsConsoleStream(handle, access, GetUseFileAPIs(handleType));          }

哈哈,终于要看到了Interop.Kernel32.GetStdHandle 这个方法就是调用系统API接口函数的方法。
Interop.GetStdHandle.cs 中调用GetStdHandle 的系统API
System.Console.csproj 的项目文件中。
可以看到,在项目文件中,使用条件编译,将不同的文件包含进来,调用不同系统的API

<!-- Windows -->    <ItemGroup Condition="'$(TargetsWindows)' == 'true'">      <Compile Include="$(CommonPath)CoreLibInteropWindowsKernel32Interop.GetStdHandle.cs">            <Link>CommonCoreLibInteropWindowsInterop.GetStdHandle.cs</Link>          </Compile>  </ItemGroup>  <!-- Unix -->  <ItemGroup Condition=" '$(TargetsUnix)' == 'true'">  </ItemGroup>

回到GetStandardFile 中看到返回一个WindowsConsoleStream
其中Read方法,调用了系统API。

              private static unsafe int ReadFileNative(IntPtr hFile, byte[] bytes, int offset, int count, bool isPipe, out int bytesRead, bool useFileAPIs)              {                  if (bytes.Length - offset < count)                      throw new IndexOutOfRangeException(SR.IndexOutOfRange_IORaceCondition);                    // You can't use the fixed statement on an array of length 0.                  if (bytes.Length == 0)                  {                      bytesRead = 0;                      return Interop.Errors.ERROR_SUCCESS;                  }                    bool readSuccess;                  fixed (byte* p = &bytes[0])                  {                      if (useFileAPIs)                      {                          readSuccess = (0 != Interop.Kernel32.ReadFile(hFile, p + offset, count, out bytesRead, IntPtr.Zero));                      }                      else                      {                          int charsRead;                          readSuccess = Interop.Kernel32.ReadConsole(hFile, p + offset, count / BytesPerWChar, out charsRead, IntPtr.Zero);                          bytesRead = charsRead * BytesPerWChar;                      }                  }                  if (readSuccess)                      return Interop.Errors.ERROR_SUCCESS;                    int errorCode = Marshal.GetLastWin32Error();                  if (errorCode == Interop.Errors.ERROR_NO_DATA || errorCode == Interop.Errors.ERROR_BROKEN_PIPE)                      return Interop.Errors.ERROR_SUCCESS;                  return errorCode;              }

useFileAPIs 参数,决定是使用操作系统 ReadFile还是 ReadConsole API。
这2个API。都是可以读取到控制台的输入流。


对于.NET CORE 源码中有很多 XXXX.Unix.cs,XXXX.Windows.cs
类名都是XXXX.例如 ConsolePal 这个内部类。
使用条件条件编译。达到不同平台使用对应的 OS API来调用。