Refactor ReadConsole P/Invoke in ConsoleHost (#9165)
Replace `StringBuilder` with stack allocated `Span<char>` to avoid extra allocation/copies during P/Invoke
This commit is contained in:
parent
43b84519a7
commit
bc95a8e088
|
@ -716,35 +716,48 @@ namespace Microsoft.PowerShell
|
|||
/// </summary>
|
||||
/// <param name="consoleHandle"></param>
|
||||
/// Handle to the console device returned by GetInputHandle
|
||||
/// <param name="initialContent">
|
||||
/// Initial contents of the edit buffer, if any. charactersToRead should be at least as large as the length of this string.
|
||||
/// <param name="initialContentLength">
|
||||
/// Length of initial content of the edit buffer. Zero if no initial content exists.
|
||||
/// Must be less than editBuffer length.
|
||||
/// </param>
|
||||
/// <param name="editBuffer">
|
||||
/// Edit buffer with optional initial content.
|
||||
/// Caution! Last position in the edit buffer is for a null in native code.
|
||||
/// </param>
|
||||
/// <param name="charactersToRead">
|
||||
/// Number of characters to read from the device.
|
||||
/// Must be less than editBuffer length.
|
||||
/// </param>
|
||||
/// <param name="endOnTab">
|
||||
/// true to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key
|
||||
/// True to allow the user to terminate input by hitting the tab or shift-tab key, in addition to the enter key
|
||||
/// </param>
|
||||
/// <param name="keyState">
|
||||
/// bit mask indicating the state of the control/shift keys at the point input was terminated.
|
||||
/// Bit mask indicating the state of the control/shift keys at the point input was terminated.
|
||||
/// </param>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="HostException">
|
||||
/// If Win32's ReadConsole fails
|
||||
/// </exception>
|
||||
|
||||
internal static string ReadConsole(ConsoleHandle consoleHandle, string initialContent,
|
||||
int charactersToRead, bool endOnTab, out uint keyState)
|
||||
internal static string ReadConsole(
|
||||
ConsoleHandle consoleHandle,
|
||||
int initialContentLength,
|
||||
Span<char> editBuffer,
|
||||
int charactersToRead,
|
||||
bool endOnTab,
|
||||
out uint keyState)
|
||||
{
|
||||
Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid");
|
||||
Dbg.Assert(!consoleHandle.IsClosed, "ConsoleHandle is closed");
|
||||
Dbg.Assert(initialContent != null, "if no initial content is desired, pass string.Empty");
|
||||
Dbg.Assert(initialContentLength < editBuffer.Length, "initialContentLength must be less than editBuffer.Length");
|
||||
Dbg.Assert(charactersToRead < editBuffer.Length, "charactersToRead must be less than editBuffer.Length");
|
||||
keyState = 0;
|
||||
|
||||
CONSOLE_READCONSOLE_CONTROL control = new CONSOLE_READCONSOLE_CONTROL();
|
||||
|
||||
control.nLength = (ULONG)Marshal.SizeOf(control);
|
||||
control.nInitialChars = (ULONG)initialContent.Length;
|
||||
control.nInitialChars = (ULONG)initialContentLength;
|
||||
control.dwControlKeyState = 0;
|
||||
if (endOnTab)
|
||||
{
|
||||
|
@ -753,28 +766,34 @@ namespace Microsoft.PowerShell
|
|||
control.dwCtrlWakeupMask = (1 << TAB);
|
||||
}
|
||||
|
||||
DWORD charsReadUnused = 0;
|
||||
StringBuilder buffer = new StringBuilder(initialContent, charactersToRead);
|
||||
DWORD charsReaded = 0;
|
||||
|
||||
bool result =
|
||||
NativeMethods.ReadConsole(
|
||||
consoleHandle.DangerousGetHandle(),
|
||||
buffer,
|
||||
editBuffer,
|
||||
(DWORD)charactersToRead,
|
||||
out charsReadUnused,
|
||||
out charsReaded,
|
||||
ref control);
|
||||
keyState = control.dwControlKeyState;
|
||||
if (result == false)
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
|
||||
HostException e = CreateHostException(err, "ReadConsole",
|
||||
ErrorCategory.ReadError, ConsoleControlStrings.ReadConsoleExceptionTemplate);
|
||||
HostException e = CreateHostException(
|
||||
err,
|
||||
"ReadConsole",
|
||||
ErrorCategory.ReadError,
|
||||
ConsoleControlStrings.ReadConsoleExceptionTemplate);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (charsReadUnused > (uint)buffer.Length)
|
||||
charsReadUnused = (uint)buffer.Length;
|
||||
return buffer.ToString(0, (int)charsReadUnused);
|
||||
if (charsReaded > (uint)charactersToRead)
|
||||
{
|
||||
charsReaded = (uint)charactersToRead;
|
||||
}
|
||||
|
||||
return editBuffer.Slice(0, (int)charsReaded).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -3019,15 +3038,30 @@ namespace Microsoft.PowerShell
|
|||
|
||||
[DllImport(PinvokeDllNames.ReadConsoleDllName, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool ReadConsole
|
||||
private static extern unsafe bool ReadConsole
|
||||
(
|
||||
NakedWin32Handle consoleInput,
|
||||
StringBuilder buffer,
|
||||
char* lpBuffer,
|
||||
DWORD numberOfCharsToRead,
|
||||
out DWORD numberOfCharsRead,
|
||||
ref CONSOLE_READCONSOLE_CONTROL controlData
|
||||
);
|
||||
|
||||
internal static unsafe bool ReadConsole
|
||||
(
|
||||
NakedWin32Handle consoleInput,
|
||||
Span<char> buffer,
|
||||
DWORD numberOfCharsToRead,
|
||||
out DWORD numberOfCharsRead,
|
||||
ref CONSOLE_READCONSOLE_CONTROL controlData
|
||||
)
|
||||
{
|
||||
fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
return ReadConsole(consoleInput, bufferPtr, numberOfCharsToRead, out numberOfCharsRead, ref controlData);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(PinvokeDllNames.PeekConsoleInputDllName, SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool PeekConsoleInput
|
||||
|
|
|
@ -307,8 +307,9 @@ namespace Microsoft.PowerShell
|
|||
#if UNIX
|
||||
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
|
||||
#else
|
||||
uint unused = 0;
|
||||
string key = ConsoleControl.ReadConsole(handle, string.Empty, 1, false, out unused);
|
||||
const int CharactersToRead = 1;
|
||||
Span<char> inputBuffer = stackalloc char[CharactersToRead + 1];
|
||||
string key = ConsoleControl.ReadConsole(handle, initialContentLength: 0, inputBuffer, charactersToRead: CharactersToRead, endOnTab: false, out _);
|
||||
#endif
|
||||
|
||||
#if UNIX
|
||||
|
@ -1287,7 +1288,7 @@ namespace Microsoft.PowerShell
|
|||
endedOnBreak = 3
|
||||
}
|
||||
|
||||
private const int maxInputLineLength = 8192;
|
||||
private const int MaxInputLineLength = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of input from the console. Returns when the user hits enter, a break key, a break event occurs. In
|
||||
|
@ -1468,13 +1469,19 @@ namespace Microsoft.PowerShell
|
|||
_rawui.ClearKeyCache();
|
||||
uint keyState = 0;
|
||||
string s = string.Empty;
|
||||
Span<char> inputBuffer = stackalloc char[MaxInputLineLength + 1];
|
||||
if (initialContent.Length > 0)
|
||||
{
|
||||
initialContent.AsSpan().CopyTo(inputBuffer);
|
||||
}
|
||||
|
||||
#endif
|
||||
do
|
||||
{
|
||||
#if UNIX
|
||||
keyInfo = Console.ReadKey(true);
|
||||
#else
|
||||
s += ConsoleControl.ReadConsole(handle, initialContent, maxInputLineLength, endOnTab, out keyState);
|
||||
s += ConsoleControl.ReadConsole(handle, initialContent.Length, inputBuffer, MaxInputLineLength, endOnTab, out keyState);
|
||||
Dbg.Assert(s != null, "s should never be null");
|
||||
#endif
|
||||
|
||||
|
@ -1861,9 +1868,9 @@ namespace Microsoft.PowerShell
|
|||
completedInput += restOfLine;
|
||||
}
|
||||
|
||||
if (completedInput.Length > (maxInputLineLength - 2))
|
||||
if (completedInput.Length > (MaxInputLineLength - 2))
|
||||
{
|
||||
completedInput = completedInput.Substring(0, maxInputLineLength - 2);
|
||||
completedInput = completedInput.Substring(0, MaxInputLineLength - 2);
|
||||
}
|
||||
|
||||
// Remove any nulls from the string...
|
||||
|
|
Loading…
Reference in a new issue