1. 程式人生 > >.NET CORE 怎麼樣從控制檯中讀取輸入流

.NET CORE 怎麼樣從控制檯中讀取輸入流

.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)\CoreLib\Interop\Windows\Kernel32\Interop.GetStdHandle.cs">
          <Link>Common\CoreLib\Interop\Windows\Interop.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來呼叫