1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Text;
7 using System.Runtime.InteropServices;
8 
9 internal partial class Interop
10 {
11     internal partial class Kernel32
12     {
13         private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
14         private const int FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
15         private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
16         private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
17 
18 
19         private const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
20 
21         [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, EntryPoint = "FormatMessageW", SetLastError = true, BestFitMapping = true)]
FormatMessage( int dwFlags, IntPtr lpSource, uint dwMessageId, int dwLanguageId, [Out] StringBuilder lpBuffer, int nSize, IntPtr[] arguments)22         private static extern int FormatMessage(
23             int dwFlags,
24             IntPtr lpSource,
25             uint dwMessageId,
26             int dwLanguageId,
27             [Out] StringBuilder lpBuffer,
28             int nSize,
29             IntPtr[] arguments);
30 
31         /// <summary>
32         ///     Returns a string message for the specified Win32 error code.
33         /// </summary>
GetMessage(int errorCode)34         internal static string GetMessage(int errorCode)
35         {
36             return GetMessage(IntPtr.Zero, errorCode);
37         }
38 
GetMessage(IntPtr moduleHandle, int errorCode)39         internal static string GetMessage(IntPtr moduleHandle, int errorCode)
40         {
41             var sb = new StringBuilder(InitialBufferSize);
42             do
43             {
44                 string errorMsg;
45                 if (TryGetErrorMessage(moduleHandle, errorCode, sb, out errorMsg))
46                 {
47                     return errorMsg;
48                 }
49                 else
50                 {
51                     // increase the capacity of the StringBuilder.
52                     sb.Capacity *= BufferSizeIncreaseFactor;
53                 }
54             }
55             while (sb.Capacity < MaxAllowedBufferSize);
56 
57             // If you come here then a size as large as 65K is also not sufficient and so we give the generic errorMsg.
58             return string.Format("Unknown error (0x{0:x})", errorCode);
59         }
60 
TryGetErrorMessage(IntPtr moduleHandle, int errorCode, StringBuilder sb, out string errorMsg)61         private static bool TryGetErrorMessage(IntPtr moduleHandle, int errorCode, StringBuilder sb, out string errorMsg)
62         {
63             errorMsg = "";
64 
65             int flags = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY;
66             if (moduleHandle != IntPtr.Zero)
67             {
68                 flags |= FORMAT_MESSAGE_FROM_HMODULE;
69             }
70 
71             int result = FormatMessage(flags, moduleHandle, (uint)errorCode, 0, sb, sb.Capacity, null);
72             if (result != 0)
73             {
74                 int i = sb.Length;
75                 while (i > 0)
76                 {
77                     char ch = sb[i - 1];
78                     if (ch > 32 && ch != '.') break;
79                     i--;
80                 }
81                 errorMsg = sb.ToString(0, i);
82             }
83             else if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
84             {
85                 return false;
86             }
87             else
88             {
89                 errorMsg = string.Format("Unknown error (0x{0:x})", errorCode);
90             }
91 
92             return true;
93         }
94 
95         // Windows API FormatMessage lets you format a message string given an errorcode.
96         // Unlike other APIs this API does not support a way to query it for the total message size.
97         //
98         // So the API can only be used in one of these two ways.
99         // a. You pass a buffer of appropriate size and get the resource.
100         // b. Windows creates a buffer and passes the address back and the onus of releasing the buffer lies on the caller.
101         //
102         // Since the error code is coming from the user, it is not possible to know the size in advance.
103         // Unfortunately we can't use option b. since the buffer can only be freed using LocalFree and it is a private API on onecore.
104         // Also, using option b is ugly for the managed code and could cause memory leak in situations where freeing is unsuccessful.
105         //
106         // As a result we use the following approach.
107         // 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.
108         private const int InitialBufferSize = 256;
109         private const int BufferSizeIncreaseFactor = 4;
110         private const int MaxAllowedBufferSize = 65 * 1024;
111     }
112 }
113