diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs | 108 |
1 files changed, 42 insertions, 66 deletions
diff --git a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs index 94722b6c8..3ea12d7cf 100644 --- a/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs +++ b/src/System.Private.CoreLib/shared/Interop/Windows/Kernel32/Interop.FormatMessage.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Text; using System.Runtime.InteropServices; internal partial class Interop @@ -14,99 +13,76 @@ internal partial class Interop private const int FORMAT_MESSAGE_FROM_HMODULE = 0x00000800; private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; - - + private const int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; private const int ERROR_INSUFFICIENT_BUFFER = 0x7A; [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "FormatMessageW", SetLastError = true, BestFitMapping = true)] - private static extern int FormatMessage( + private static extern unsafe int FormatMessage( int dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, - [Out] StringBuilder lpBuffer, + void* lpBuffer, int nSize, - IntPtr[] arguments); + IntPtr arguments); /// <summary> /// Returns a string message for the specified Win32 error code. /// </summary> - internal static string GetMessage(int errorCode) - { - return GetMessage(IntPtr.Zero, errorCode); - } - - internal static string GetMessage(IntPtr moduleHandle, int errorCode) - { - var sb = new StringBuilder(InitialBufferSize); - do - { - string errorMsg; - if (TryGetErrorMessage(moduleHandle, errorCode, sb, out errorMsg)) - { - return errorMsg; - } - else - { - // increase the capacity of the StringBuilder. - sb.Capacity *= BufferSizeIncreaseFactor; - } - } - while (sb.Capacity < MaxAllowedBufferSize); - - // If you come here then a size as large as 65K is also not sufficient and so we give the generic errorMsg. - return string.Format("Unknown error (0x{0:x})", errorCode); - } + internal static string GetMessage(int errorCode) => + GetMessage(errorCode, IntPtr.Zero); - private static bool TryGetErrorMessage(IntPtr moduleHandle, int errorCode, StringBuilder sb, out string errorMsg) + internal static unsafe string GetMessage(int errorCode, IntPtr moduleHandle) { - errorMsg = ""; - int flags = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY; if (moduleHandle != IntPtr.Zero) { flags |= FORMAT_MESSAGE_FROM_HMODULE; } - int result = FormatMessage(flags, moduleHandle, (uint)errorCode, 0, sb, sb.Capacity, null); - if (result != 0) + // First try to format the message into the stack based buffer. Most error messages willl fit. + Span<char> stackBuffer = stackalloc char[256]; // arbitrary stack limit + fixed (char* bufferPtr = &MemoryMarshal.GetReference(stackBuffer)) { - int i = sb.Length; - while (i > 0) + int length = FormatMessage(flags, moduleHandle, unchecked((uint)errorCode), 0, bufferPtr, stackBuffer.Length, IntPtr.Zero); + if (length > 0) { - char ch = sb[i - 1]; - if (ch > 32 && ch != '.') break; - i--; + return GetAndTrimString(stackBuffer.Slice(0, length)); } - errorMsg = sb.ToString(0, i); - } - else if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER) - { - return false; } - else + + // We got back an error. If the error indicated that there wasn't enough room to store + // the error message, then call FormatMessage again, but this time rather than passing in + // a buffer, have the method allocate one, which we then need to free. + if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER) { - errorMsg = string.Format("Unknown error (0x{0:x})", errorCode); + IntPtr nativeMsgPtr = default; + try + { + int length = FormatMessage(flags | FORMAT_MESSAGE_ALLOCATE_BUFFER, moduleHandle, unchecked((uint)errorCode), 0, &nativeMsgPtr, 0, IntPtr.Zero); + if (length > 0) + { + return GetAndTrimString(new Span<char>((char*)nativeMsgPtr, length)); + } + } + finally + { + Marshal.FreeHGlobal(nativeMsgPtr); + } } - return true; + // Couldn't get a message, so manufacture one. + return string.Format("Unknown error (0x{0:x})", errorCode); } - // Windows API FormatMessage lets you format a message string given an errorcode. - // Unlike other APIs this API does not support a way to query it for the total message size. - // - // So the API can only be used in one of these two ways. - // a. You pass a buffer of appropriate size and get the resource. - // b. Windows creates a buffer and passes the address back and the onus of releasing the buffer lies on the caller. - // - // Since the error code is coming from the user, it is not possible to know the size in advance. - // Unfortunately we can't use option b. since the buffer can only be freed using LocalFree and it is a private API on onecore. - // Also, using option b is ugly for the managed code and could cause memory leak in situations where freeing is unsuccessful. - // - // As a result we use the following approach. - // We initially call the API with a buffer size of 256 and then gradually increase the size in case of failure until we reach the maximum allowed limit of 65K. - private const int InitialBufferSize = 256; - private const int BufferSizeIncreaseFactor = 4; - private const int MaxAllowedBufferSize = 65 * 1024; + private static string GetAndTrimString(Span<char> buffer) + { + int length = buffer.Length; + while (length > 0 && buffer[length - 1] <= 32) + { + length--; // trim off spaces and non-printable ASCII chars at the end of the resource + } + return buffer.Slice(0, length).ToString(); + } } } |