diff --git a/src-native-c/THNETII.WinApi.Sample.Native/main.c b/src-native-c/THNETII.WinApi.Sample.Native/main.c index d00df0ce..c4bf429f 100644 --- a/src-native-c/THNETII.WinApi.Sample.Native/main.c +++ b/src-native-c/THNETII.WinApi.Sample.Native/main.c @@ -1,12 +1,12 @@ -#include +#include int main(int argc, char* argv[]) { - ACE_HEADER instance; + DWORD_PTR instance; const int size = sizeof(instance); - const int value = ACE_OBJECT_TYPE_PRESENT; + const int value = FORMAT_MESSAGE_ALLOCATE_BUFFER; - const void* ptr = AccFree; + const void* ptr = FormatMessage; return EXIT_SUCCESS; } diff --git a/src-native/THNETII.WinApi.Headers.ErrHandlingApi/ErrHandlingApiFunctions.cs b/src-native/THNETII.WinApi.Headers.ErrHandlingApi/ErrHandlingApiFunctions.cs index 380e3fe2..bfd65f05 100644 --- a/src-native/THNETII.WinApi.Headers.ErrHandlingApi/ErrHandlingApiFunctions.cs +++ b/src-native/THNETII.WinApi.Headers.ErrHandlingApi/ErrHandlingApiFunctions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; using System.Text; using THNETII.WinApi.Native.WinBase; @@ -25,7 +25,7 @@ public static class ErrHandlingApiFunctions /// /// /// Functions executed by the calling thread set this value by calling the function. You should call the function immediately when a function's return value indicates that such a call will return useful data. That is because some functions call with a zero when they succeed, wiping out the error code set by the most recently failed function. - /// To obtain an error string for system error codes, use the function. For a complete list of error codes provided by the operating system, see System Error Codes. + /// To obtain an error string for system error codes, use the function. For a complete list of error codes provided by the operating system, see System Error Codes. /// The error codes returned by a function are not part of the Windows API specification and can vary by operating system or device driver. For this reason, we cannot provide the complete list of error codes that can be returned by each function. There are also many functions whose documentation does not include even a partial list of error codes that can be returned. /// Error codes are 32-bit values (bit 31 is the most significant bit). Bit 29 is reserved for application-defined error codes; no system error code has this bit set. If you are defining an error code for your application, set this bit to one. That indicates that the error code has been defined by an application, and ensures that your error code does not conflict with any error codes defined by the system. /// To convert a system error into an value, use the function. @@ -41,7 +41,7 @@ public static class ErrHandlingApiFunctions /// /// The native library containg the function could not be found. /// Unable to find the entry point for the function in the native library. - /// + /// /// /// /// diff --git a/src-native/THNETII.WinApi.Headers.WinBase/FORMAT_MESSAGE_OPTIONS.cs b/src-native/THNETII.WinApi.Headers.WinBase/FORMAT_MESSAGE_OPTIONS.cs deleted file mode 100644 index aa1732ce..00000000 --- a/src-native/THNETII.WinApi.Headers.WinBase/FORMAT_MESSAGE_OPTIONS.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using THNETII.InteropServices.Bitwise; - -namespace THNETII.WinApi.Native.WinBase -{ - using static WinBaseConstants; - - [StructLayout(LayoutKind.Sequential)] - [DebuggerDisplay(nameof(DebuggerDisplay) + "()")] - public struct FORMAT_MESSAGE_OPTIONS : IEquatable, IEquatable, IEquatable - { - private static readonly Bitfield32 width_field = Bitfield32.FromMask(FORMAT_MESSAGE_MAX_WIDTH_MASK); - private static readonly Bitfield32 flags_field = Bitfield32.FromMask(width_field.InverseMask); - - internal int dwFlags; - - public int Width - { - get => width_field.Read(dwFlags); - set => width_field.Write(ref dwFlags, value); - } - - public FORMAT_MESSAGE_FLAGS Flags - { - get => (FORMAT_MESSAGE_FLAGS)flags_field.Read(dwFlags); - set => flags_field.Write(ref dwFlags, (int)value); - } - - [SuppressMessage("Usage", "CA2225: Operator overloads have named alternates")] - public static explicit operator int(FORMAT_MESSAGE_OPTIONS options) => - options.dwFlags; - - [SuppressMessage("Usage", "CA2225: Operator overloads have named alternates")] - public static explicit operator FORMAT_MESSAGE_OPTIONS(int dwFlags) => - new FORMAT_MESSAGE_OPTIONS(dwFlags); - - [SuppressMessage("Usage", "CA2225: Operator overloads have named alternates")] - public static implicit operator FORMAT_MESSAGE_OPTIONS(FORMAT_MESSAGE_FLAGS dwFlags) => - new FORMAT_MESSAGE_OPTIONS(dwFlags); - - public FORMAT_MESSAGE_OPTIONS(int dwFlags) => this.dwFlags = dwFlags; - - public FORMAT_MESSAGE_OPTIONS(FORMAT_MESSAGE_FLAGS dwFlags) : this((int)dwFlags) { } - - public override int GetHashCode() => dwFlags.GetHashCode(); - - public override bool Equals(object obj) - { - switch (obj) - { - case null: return false; - case FORMAT_MESSAGE_OPTIONS options: return dwFlags == options.dwFlags; - case int dwFlags: return this.dwFlags == dwFlags; - case FORMAT_MESSAGE_FLAGS dwFlags: return this.dwFlags == (int)dwFlags; - default: return false; - } - } - - public bool Equals(FORMAT_MESSAGE_OPTIONS options) => dwFlags == options.dwFlags; - - public bool Equals(int dwFlags) => this.dwFlags == dwFlags; - - public bool Equals(FORMAT_MESSAGE_FLAGS dwFlags) => this.dwFlags == (int)dwFlags; - - public static bool operator ==(FORMAT_MESSAGE_OPTIONS left, FORMAT_MESSAGE_OPTIONS right) => - left.Equals(right); - - public static bool operator ==(FORMAT_MESSAGE_OPTIONS left, int right) => - left.Equals(right); - - public static bool operator ==(FORMAT_MESSAGE_OPTIONS left, FORMAT_MESSAGE_FLAGS right) => - left.Equals(right); - - public static bool operator ==(int left, FORMAT_MESSAGE_OPTIONS right) => - right.Equals(left); - - public static bool operator ==(FORMAT_MESSAGE_FLAGS left, FORMAT_MESSAGE_OPTIONS right) => - right.Equals(left); - - public static bool operator !=(FORMAT_MESSAGE_OPTIONS left, FORMAT_MESSAGE_OPTIONS right) => - !left.Equals(right); - - public static bool operator !=(FORMAT_MESSAGE_OPTIONS left, int right) => - !left.Equals(right); - - public static bool operator !=(FORMAT_MESSAGE_OPTIONS left, FORMAT_MESSAGE_FLAGS right) => - !left.Equals(right); - - public static bool operator !=(int left, FORMAT_MESSAGE_OPTIONS right) => - !right.Equals(left); - - public static bool operator !=(FORMAT_MESSAGE_FLAGS left, FORMAT_MESSAGE_OPTIONS right) => - !right.Equals(left); - - private string DebuggerDisplay() => $"{nameof(FORMAT_MESSAGE_OPTIONS)}({nameof(Flags)}: {Flags}; {nameof(Width)}: {Width})"; - } -} diff --git a/src-native/THNETII.WinApi.Headers.WinBase/GlobalSuppressions.cs b/src-native/THNETII.WinApi.Headers.WinBase/GlobalSuppressions.cs index 9bbfce3f..df2a1295 100644 --- a/src-native/THNETII.WinApi.Headers.WinBase/GlobalSuppressions.cs +++ b/src-native/THNETII.WinApi.Headers.WinBase/GlobalSuppressions.cs @@ -1,4 +1,4 @@ - + // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given @@ -6,11 +6,21 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Interoperability", "CA1401: P/Invokes should not be visible")] -[assembly: SuppressMessage("Usage", "PC003: Native API not available in UWP")] -[assembly: SuppressMessage("Globalization", "CA2101: Specify marshaling for P/Invoke string arguments")] -[assembly: SuppressMessage("Naming", "CA1707: Identifiers should not contain underscores")] -[assembly: SuppressMessage("Design", "CA1051: Do not declare visible instance fields")] -[assembly: SuppressMessage("Performance", "CA1815: Override equals and operator equals on value types")] -[assembly: SuppressMessage("Naming", "CA1714: Flags enums should have plural names")] -[assembly: SuppressMessage("Documentation", "CA1200: Avoid using cref tags with a prefix")] +[assembly: SuppressMessage("Design", + "CA1051: Do not declare visible instance fields")] +[assembly: SuppressMessage("Documentation", + "CA1200: Avoid using cref tags with a prefix")] +[assembly: SuppressMessage("Interoperability", + "CA1401: P/Invokes should not be visible")] +[assembly: SuppressMessage("Naming", + "CA1707: Identifiers should not contain underscores")] +[assembly: SuppressMessage("Naming", + "CA1714: Flags enums should have plural names")] +[assembly: SuppressMessage("Performance", + "CA1815: Override equals and operator equals on value types")] +[assembly: SuppressMessage("Globalization", + "CA2101: Specify marshaling for P/Invoke string arguments")] +[assembly: SuppressMessage("Documentation", + "CS0419: Ambiguous reference in cref attribute")] +[assembly: SuppressMessage("Usage", + "PC003: Native API not available in UWP")] diff --git a/src-native/THNETII.WinApi.Headers.WinBase/THNETII.WinApi.Headers.WinBase.csproj b/src-native/THNETII.WinApi.Headers.WinBase/THNETII.WinApi.Headers.WinBase.csproj index 1c5a2fef..24a96c4a 100644 --- a/src-native/THNETII.WinApi.Headers.WinBase/THNETII.WinApi.Headers.WinBase.csproj +++ b/src-native/THNETII.WinApi.Headers.WinBase/THNETII.WinApi.Headers.WinBase.csproj @@ -2,11 +2,12 @@ - 7.2 + 8 + enable true netstandard1.3;netstandard1.6;netstandard2.0 true - CS1591 + $(NoWarn);CS0419;CS1591 THNETII.WinApi.Native.WinBase diff --git a/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.ErrHandlingApi.cs b/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.ErrHandlingApi.cs new file mode 100644 index 00000000..e0500a15 --- /dev/null +++ b/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.ErrHandlingApi.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace THNETII.WinApi.Native.WinBase +{ + + public static partial class WinBaseFunctions + { + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\errhandlingapi.h, line 96 + #region SetLastError function + /// + [DllImport(NativeLibraryNames.Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + private static extern void SetLastError( + [In] int dwErrCode + ); + #endregion + } +} diff --git a/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.MinWinBase.cs b/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.MinWinBase.cs index 9aef7ede..b8baf4c4 100644 --- a/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.MinWinBase.cs +++ b/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.MinWinBase.cs @@ -1,8 +1,11 @@ -using System; +using System; using System.Runtime.InteropServices; + using THNETII.WinApi.Native.MinWinDef; + using static THNETII.WinApi.Native.MinWinBase.LMEM_FLAGS; -#if NETSTANDARD1_3 + +#if NETSTANDARD1_6 using EntryPointNotFoundException = System.Exception; #endif diff --git a/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.cs b/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.cs index 922f4626..4af59802 100644 --- a/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.cs +++ b/src-native/THNETII.WinApi.Headers.WinBase/WinBaseFunctions.cs @@ -1,24 +1,55 @@ -using System; +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; + +using THNETII.InteropServices.Memory; + using THNETII.WinApi.Native.MinWinBase; using THNETII.WinApi.Native.MinWinDef; +using THNETII.WinApi.Native.WinError; -using static System.Runtime.InteropServices.CallingConvention; -using static THNETII.WinApi.Native.MinWinBase.LMEM_FLAGS; -using static THNETII.WinApi.Native.WinBase.FORMAT_MESSAGE_FLAGS; -using static THNETII.WinApi.Native.WinError.WinErrorConstants; - -#if NETSTANDARD1_6 +#if NETSTANDARD1_3 || NETSTANDARD1_6 using AccessViolationException = System.Exception; using EntryPointNotFoundException = System.Exception; #endif namespace THNETII.WinApi.Native.WinBase { + using static FORMAT_MESSAGE_FLAGS; + using static LMEM_FLAGS; + using static NativeLibraryNames; + using static WinErrorConstants; public static partial class WinBaseFunctions { + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 82 + #region DefineHandleTable macro + // #define DefineHandleTable(w) ((w),TRUE) + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 83 + #region LimitEmsPages macro + [SuppressMessage("Style", "IDE0060: Remove unused parameter")] + [SuppressMessage("Usage", "CA1801: Review unused parameters")] + public static void LimitEmsPages(int dw) { } + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 84 + #region SetSwapAreaSize macro + // #define SetSwapAreaSize(w) (w) + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 85 + #region LockSegment macro + // #define LockSegment(w) GlobalFix((HANDLE)(w)) + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 86 + #region UnlockSegment macro + // #define UnlockSegment(w) GlobalUnfix((HANDLE)(w)) + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 94 + #region GetCurrentTime macro + // TODO: #define GetCurrentTime() GetTickCount() + #endregion // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 116 #region CaptureStackBackTrace function /// @@ -116,7 +147,7 @@ out int BackTraceHash /// /// /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern HLOCAL LocalAlloc( [MarshalAs(UnmanagedType.I4)] LMEM_FLAGS uFlags, [MarshalAs(UnmanagedType.SysUInt)] UIntPtr uBytes @@ -167,7 +198,7 @@ public static extern HLOCAL LocalAlloc( /// /// /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern HLOCAL LocalReAlloc( HLOCAL hMem, [MarshalAs(UnmanagedType.SysUInt)] UIntPtr uBytes, @@ -205,7 +236,7 @@ public static extern HLOCAL LocalReAlloc( /// /// /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern IntPtr LocalLock( HLOCAL hMem ); @@ -238,7 +269,7 @@ HLOCAL hMem /// /// /// Memory Management Functions - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern HLOCAL LocalHandle(IntPtr pMem); #endregion // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 1138 @@ -273,7 +304,7 @@ HLOCAL hMem /// /// /// Memory Management Functions - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern int LocalUnlock(HLOCAL hMem); #endregion // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 1151 @@ -304,7 +335,7 @@ HLOCAL hMem /// /// /// Memory Management Functions - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern UIntPtr LocalSize(HLOCAL hMem); #endregion #region LocalFlags function @@ -338,7 +369,7 @@ HLOCAL hMem /// /// /// Memory Management Functions - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] [return: MarshalAs(UnmanagedType.I4)] public static extern LMEM_STATUS LocalFlags(HLOCAL hMem); #endregion @@ -377,7 +408,7 @@ HLOCAL hMem /// /// /// Memory Management Functions - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, SetLastError = true)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, SetLastError = true)] public static extern HLOCAL LocalFree(HLOCAL hMem); #endregion // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 1350 @@ -400,437 +431,873 @@ HLOCAL hMem /// The native library containg the function could not be found. /// Unable to find the entry point for the function in the native library. /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi)] + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi)] public static extern void FatalExit( int ExitCode ); #endregion // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 2382 - #region FormatMessage function - /// - public static int FormatMessage( - FORMAT_MESSAGE_OPTIONS dwFlags, - IntPtr lpSource, + #region FormatMessageA function + // string lpSource + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + string lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, lpBuffer?.Capacity ?? 0, Arguments); - /// - /// Formats a message string. The function requires a message definition as input. - /// It can come from a message table resource in an already-loaded module. - /// Or the caller can ask the function to search the system's message table resource(s) for the message definition. - /// The function finds the message definition in a message table resource based on a message identifier and a language identifier. - /// The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested. - /// - /// - /// - /// The formatting options, and how to interpret the parameter. - /// The property specifies how the function handles line breaks in the output buffer. It can also specify the maximum width of a formatted output line. - /// - /// - /// The function ignores regular line breaks in the message definition text. The function never splits a string delimited by white space across a line break. The function stores hard-coded line breaks in the message definition text into the output buffer. Hard-coded line breaks are coded with the %n escape sequence. - /// - /// - /// - /// The location of the message definition. The type of this parameter depends upon the settings in the parameter. - /// - /// Meaning - /// A handle to the module that contains the message table to search. - /// Pointer to a string that consists of unformatted message text. It will be scanned for inserts and formatted accordingly. - /// - /// If neither of these flags is set in , then is ignored. - /// - /// The message identifier for the requested message. This parameter is ignored if includes . - /// - /// The language identifier for the requested message. This parameter is ignored if includes . - /// - /// If you pass a specific LCID in this parameter, will return a message for that LCID only. - /// If the function cannot find a message for that LCID, it sets Last-Error to . - /// If you pass in 0 (zero), looks for a message for LCIDs in the following order: - /// - /// Language neutral - /// Thread LCID, based on the thread's locale value - /// User default LCID, based on the user's default locale value - /// System default LCID, based on the system default locale value - /// US English - /// - /// - /// - /// If does not locate a message for any of the preceding LCIDs, - /// it returns any language message string that is present. If that fails, it returns . - /// - /// - /// - /// A buffer that receives the the string that specifies the formatted message. - /// DO NOT use this overload with the flag set in ! - /// - /// Specifies the of . - /// - /// A pointer to an array of values that are used as insert values in the formatted message. A %1 in the format string indicates the first value in the Arguments array; a %2 indicates the second argument; and so on. - /// The interpretation of each value depends on the formatting information associated with the insert in the message definition. The default is to treat each value as a pointer to a null-terminated unicode string. - /// DO NOT use this parameter if the flag is not set in ! - /// The default value of can be used to indicate no insert arguments. This should be used when the flag is set in - /// - /// - /// - /// If the function succeeds, the return value is the number of characters stored in the output buffer, excluding the terminating null character. - /// - /// If the function fails, the return value is 0 (zero). To get extended error information, call . - /// - /// - /// - /// Within the message text, several escape sequences are supported for dynamically formatting the message. These escape sequences and their meanings are shown in the following tables. All escape sequences start with the percent character (%). - /// - /// Escape sequenceMeaning - /// %0Terminates a message text line without a trailing new line character. This escape sequence can be used to build up long lines or to terminate the message itself without a trailing new line character. It is useful for prompt messages. - /// - /// %n!format string! - /// - /// Identifies an insert. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. For more information, see Format Specification Fields. - /// The format string can include a width and precision specifier for strings and a width specifier for integers. Use an asterisk (*) to specify the width and precision. For example, %1!.*s! or %1!*u!. - /// If you do not use the width and precision specifiers, the insert numbers correspond directly to the input arguments. For example, if the source string is "%1 %2 %1" and the input arguments are "Bill" and "Bob", the formatted output string is "Bill Bob Bill". - /// However, if you use a width and precision specifier, the insert numbers do not correspond directly to the input arguments. For example, the insert numbers for the previous example could change to "%1!*.*s! %4 %5!*s!". - /// The next insert number is n + 2 if the previous format string contained one asterisk and is n + 3 if two asterisks were specified. - /// If you want to repeat "Bill", as in the previous example, the arguments must include "Bill" twice. For example, if the source string is "%1!*.*s! %4 %5!*s!", the arguments could be, 4, 2, "Bill", "Bob", 6, "Bill" (if using the flag). The formatted string would then be " Bi Bob Bill". - /// Repeating insert numbers when the source string contains width and precision specifiers may not yield the intended results. If you replaced %5 with %1, the function would try to print a string at address 6 (likely resulting in an access violation). - /// Floating-point format specifiers—e, E, f, and g—are not supported. The workaround is to pre-format floating-point number using the regular method and then insert that string. - /// Inserts that use the I64 prefix are treated as two 32-bit arguments. - /// - /// - /// - /// - /// - /// Any other nondigit character following a percent character is formatted in the output message without the percent character. Following are some examples. - /// - /// Format stringResulting output - /// %%A single percent sign. - /// %spaceA single space. This format string can be used to ensure the appropriate number of trailing spaces in a message text line. - /// %.A single period. This format string can be used to include a single period at the beginning of a line without terminating the message text definition. - /// %!A single exclamation point. This format string can be used to include an exclamation point immediately after an insert without its being mistaken for the beginning of a format string. - /// %nA hard line break when the format string occurs at the end of a line. This format string is useful when is supplying regular line breaks so the message fits in a certain width. - /// %rA hard carriage return without a trailing newline character. - /// %tA single tab. - /// - /// - /// - /// Security Remarks
- /// If this function is called without , the parameter must contain enough parameters to satisfy all insertion sequences in the message string, and they must be of the correct type. Therefore, do not use untrusted or unknown message strings with inserts enabled because they can contain more insertion sequences than provides, or those that may be of the wrong type. In particular, it is unsafe to take an arbitrary system error code returned from an API and use without . - ///
- /// - /// - /// Requirements - /// Minimum supported client:Windows XP [desktop apps | UWP apps] - /// Minimum supported server:Windows Server 2003 [desktop apps | UWP apps] - /// - /// - /// Microsoft Docs page: FormatMessageW function - ///
- /// The native library containg the function could not be found. - /// Unable to find the entry point for the function in the native library. - public static int FormatMessage( - FORMAT_MESSAGE_OPTIONS dwFlags, - IntPtr lpSource, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_FROM_STRING | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FROM_HMODULE + ); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBuffer, + lpBuffer?.Capacity ?? 0, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] string lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [Out] StringBuilder lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + string lpSource, int dwMessageId, int dwLanguageId, - StringBuilder lpBuffer, - int nSize, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments); - /// - public static int FormatMessageA( - FORMAT_MESSAGE_OPTIONS dwFlags, + out string lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FROM_HMODULE + ); + + HLOCAL lpBufferPointer; + int result; + fixed (IntPtr* pArguments = Arguments) + result = FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBufferPointer, + nSize, + pArguments + ); + int lastError = Marshal.GetLastWin32Error(); + lpBuffer = Marshal.PtrToStringAnsi(lpBufferPointer.Pointer, result); + if (lpBufferPointer.Pointer != IntPtr.Zero) + { + try + { + if (LocalFree(lpBufferPointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + SetLastError(lastError); + } + } + return result; + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] string lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + byte* lpSource, + int dwMessageId, + int dwLanguageId, + Span lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FROM_HMODULE + ); + fixed (byte* lpBufferPtr = lpBuffer) + fixed (IntPtr* pArguments = Arguments) + return FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBufferPtr, + lpBuffer.Length, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] byte* lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [In] byte* lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + byte* lpSource, + int dwMessageId, + int dwLanguageId, + out HLOCAL lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FROM_HMODULE + ); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBuffer, + nSize, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] byte* lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + // IntPtr lpSource + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, IntPtr lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, - IntPtr Arguments - ) => FormatMessageA(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, lpBuffer?.Capacity ?? 0, Arguments); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageA), SetLastError = true)] - public static extern int FormatMessageA( - FORMAT_MESSAGE_OPTIONS dwFlags, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~(FORMAT_MESSAGE_ALLOCATE_BUFFER); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBuffer, + lpBuffer?.Capacity ?? 0, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [Out] StringBuilder lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, IntPtr lpSource, int dwMessageId, int dwLanguageId, - [MarshalAs(UnmanagedType.LPStr)] StringBuilder lpBuffer, - int nSize, - IntPtr Arguments + out string lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + + HLOCAL lpBufferPointer; + int result; + fixed (IntPtr* pArguments = Arguments) + result = FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBufferPointer, + nSize, + pArguments + ); + int lastError = Marshal.GetLastWin32Error(); + lpBuffer = Marshal.PtrToStringAnsi(lpBufferPointer.Pointer, result); + if (lpBufferPointer.Pointer != IntPtr.Zero) + { + try + { + if (LocalFree(lpBufferPointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + SetLastError(lastError); + } + } + return result; + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments ); - /// - public static int FormatMessageW( - FORMAT_MESSAGE_OPTIONS dwFlags, + + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, IntPtr lpSource, int dwMessageId, int dwLanguageId, - StringBuilder lpBuffer, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, lpBuffer?.Capacity ?? 0, Arguments); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageW), SetLastError = true)] - public static extern int FormatMessageW( - FORMAT_MESSAGE_OPTIONS dwFlags, + Span lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~(FORMAT_MESSAGE_ALLOCATE_BUFFER); + fixed (byte* lpBufferPtr = lpBuffer) + fixed (IntPtr* pArguments = Arguments) + return FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBufferPtr, + lpBuffer.Length, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi, SetLastError = true)] + private static extern unsafe int FormatMessageA( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [In] byte* lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageA( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, IntPtr lpSource, int dwMessageId, int dwLanguageId, - [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpBuffer, - int nSize, - IntPtr Arguments - ); - /// - public static int FormatMessage( - FORMAT_MESSAGE_OPTIONS dwFlags, + out HLOCAL lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageA( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBuffer, + nSize, + pArguments + ); + } + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 2398 + #region FormatMessageW function + // string lpSource + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, string lpSource, int dwMessageId, int dwLanguageId, StringBuilder lpBuffer, - IntPtr Arguments - ) => FormatMessage(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, lpBuffer?.Capacity ?? 0, Arguments); - /// - /// Formats a message string. The function requires a message definition as input. - /// It can come from a message table resource in an already-loaded module. - /// Or the caller can ask the function to search the system's message table resource(s) for the message definition. - /// The function finds the message definition in a message table resource based on a message identifier and a language identifier. - /// The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested. - /// - /// - /// - /// The formatting options, and how to interpret the parameter. - /// The property specifies how the function handles line breaks in the output buffer. It can also specify the maximum width of a formatted output line. - /// - /// - /// The function ignores regular line breaks in the message definition text. The function never splits a string delimited by white space across a line break. The function stores hard-coded line breaks in the message definition text into the output buffer. Hard-coded line breaks are coded with the %n escape sequence. - /// - /// - /// - /// A string that consists of unformatted message text. It will be scanned for inserts and formatted accordingly. - /// Only use this overload if the flag is set in . Otherwise, set this parameter to . - /// - /// The message identifier for the requested message. This parameter is ignored if includes . - /// - /// The language identifier for the requested message. This parameter is ignored if includes . - /// - /// If you pass a specific LCID in this parameter, will return a message for that LCID only. - /// If the function cannot find a message for that LCID, it sets Last-Error to . - /// If you pass in 0 (zero), looks for a message for LCIDs in the following order: - /// - /// Language neutral - /// Thread LCID, based on the thread's locale value - /// User default LCID, based on the user's default locale value - /// System default LCID, based on the system default locale value - /// US English - /// - /// - /// - /// If does not locate a message for any of the preceding LCIDs, - /// it returns any language message string that is present. If that fails, it returns . - /// - /// - /// - /// A buffer that receives the the string that specifies the formatted message. - /// DO NOT use this overload with the flag set in ! - /// - /// Specifies the of . - /// - /// A pointer to an array of values that are used as insert values in the formatted message. A %1 in the format string indicates the first value in the Arguments array; a %2 indicates the second argument; and so on. - /// The interpretation of each value depends on the formatting information associated with the insert in the message definition. The default is to treat each value as a pointer to a null-terminated unicode string. - /// DO NOT use this parameter if the flag is not set in ! - /// The default value of can be used to indicate no insert arguments. This should be used when the flag is set in - /// - /// - /// - /// If the function succeeds, the return value is the number of characters stored in the output buffer, excluding the terminating null character. - /// - /// If the function fails, the return value is 0 (zero). To get extended error information, call . - /// - /// - /// - /// Within the message text, several escape sequences are supported for dynamically formatting the message. These escape sequences and their meanings are shown in the following tables. All escape sequences start with the percent character (%). - /// - /// Escape sequenceMeaning - /// %0Terminates a message text line without a trailing new line character. This escape sequence can be used to build up long lines or to terminate the message itself without a trailing new line character. It is useful for prompt messages. - /// - /// %n!format string! - /// - /// Identifies an insert. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. For more information, see Format Specification Fields. - /// The format string can include a width and precision specifier for strings and a width specifier for integers. Use an asterisk (*) to specify the width and precision. For example, %1!.*s! or %1!*u!. - /// If you do not use the width and precision specifiers, the insert numbers correspond directly to the input arguments. For example, if the source string is "%1 %2 %1" and the input arguments are "Bill" and "Bob", the formatted output string is "Bill Bob Bill". - /// However, if you use a width and precision specifier, the insert numbers do not correspond directly to the input arguments. For example, the insert numbers for the previous example could change to "%1!*.*s! %4 %5!*s!". - /// The next insert number is n + 2 if the previous format string contained one asterisk and is n + 3 if two asterisks were specified. - /// If you want to repeat "Bill", as in the previous example, the arguments must include "Bill" twice. For example, if the source string is "%1!*.*s! %4 %5!*s!", the arguments could be, 4, 2, "Bill", "Bob", 6, "Bill" (if using the flag). The formatted string would then be " Bi Bob Bill". - /// Repeating insert numbers when the source string contains width and precision specifiers may not yield the intended results. If you replaced %5 with %1, the function would try to print a string at address 6 (likely resulting in an access violation). - /// Floating-point format specifiers—e, E, f, and g—are not supported. The workaround is to pre-format floating-point number using the regular method and then insert that string. - /// Inserts that use the I64 prefix are treated as two 32-bit arguments. - /// - /// - /// - /// - /// - /// Any other nondigit character following a percent character is formatted in the output message without the percent character. Following are some examples. - /// - /// Format stringResulting output - /// %%A single percent sign. - /// %spaceA single space. This format string can be used to ensure the appropriate number of trailing spaces in a message text line. - /// %.A single period. This format string can be used to include a single period at the beginning of a line without terminating the message text definition. - /// %!A single exclamation point. This format string can be used to include an exclamation point immediately after an insert without its being mistaken for the beginning of a format string. - /// %nA hard line break when the format string occurs at the end of a line. This format string is useful when is supplying regular line breaks so the message fits in a certain width. - /// %rA hard carriage return without a trailing newline character. - /// %tA single tab. - /// - /// - /// - /// Security Remarks
- /// If this function is called without , the parameter must contain enough parameters to satisfy all insertion sequences in the message string, and they must be of the correct type. Therefore, do not use untrusted or unknown message strings with inserts enabled because they can contain more insertion sequences than provides, or those that may be of the wrong type. In particular, it is unsafe to take an arbitrary system error code returned from an API and use without . - ///
- /// - /// - /// Requirements - /// Minimum supported client:Windows XP [desktop apps | UWP apps] - /// Minimum supported server:Windows Server 2003 [desktop apps | UWP apps] - /// - /// - /// Microsoft Docs page: FormatMessageW function - ///
- /// The native library containg the function could not be found. - /// Unable to find the entry point for the function in the native library. - public static int FormatMessage( - FORMAT_MESSAGE_OPTIONS dwFlags, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBuffer, + lpBuffer?.Capacity ?? 0, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] string lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [Out] StringBuilder lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, string lpSource, int dwMessageId, int dwLanguageId, - StringBuilder lpBuffer, - int nSize, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments); - /// - public static int FormatMessageA( - FORMAT_MESSAGE_OPTIONS dwFlags, - [MarshalAs(UnmanagedType.LPStr)] string lpSource, + out string lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + HLOCAL lpBufferPointer; + int result; + fixed (IntPtr* pArguments = Arguments) + result = FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBufferPointer, + nSize, + pArguments + ); + int lastError = Marshal.GetLastWin32Error(); + lpBuffer = Marshal.PtrToStringUni(lpBufferPointer.Pointer, result); + if (lpBufferPointer.Pointer != IntPtr.Zero) + { + try + { + if (LocalFree(lpBufferPointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + SetLastError(lastError); + } + } + return result; + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] string lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + char* lpSource, + int dwMessageId, + int dwLanguageId, + Span lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + fixed (char* lpBufferPtr = lpBuffer) + fixed (IntPtr* pArguments = Arguments) + return FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBufferPtr, + lpBuffer.Length, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] char* lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [In] char* lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + char* lpSource, int dwMessageId, int dwLanguageId, - [MarshalAs(UnmanagedType.LPStr)] StringBuilder lpBuffer, - IntPtr Arguments - ) => FormatMessageA(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, lpBuffer?.Capacity ?? 0, Arguments); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageA), SetLastError = true)] - public static extern int FormatMessageA( - FORMAT_MESSAGE_OPTIONS dwFlags, - [MarshalAs(UnmanagedType.LPStr)] string lpSource, + out HLOCAL lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBuffer, + nSize, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] char* lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + // IntPtr lpSource + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, int dwMessageId, int dwLanguageId, - [MarshalAs(UnmanagedType.LPStr)] StringBuilder lpBuffer, - int nSize, - IntPtr Arguments + StringBuilder lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~(FORMAT_MESSAGE_ALLOCATE_BUFFER); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBuffer, + lpBuffer?.Capacity ?? 0, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [Out] StringBuilder lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments ); - /// - public static int FormatMessageW( - FORMAT_MESSAGE_OPTIONS dwFlags, - [MarshalAs(UnmanagedType.LPWStr)] string lpSource, + + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, int dwMessageId, int dwLanguageId, - [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpBuffer, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, lpBuffer?.Capacity ?? 0, Arguments); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageW), SetLastError = true)] - public static extern int FormatMessageW( - FORMAT_MESSAGE_OPTIONS dwFlags, - [MarshalAs(UnmanagedType.LPWStr)] string lpSource, + out string lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + + HLOCAL lpBufferPointer; + int result; + fixed (IntPtr* pArguments = Arguments) + result = FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBufferPointer, + nSize, + pArguments + ); + int lastError = Marshal.GetLastWin32Error(); + lpBuffer = Marshal.PtrToStringUni(lpBufferPointer.Pointer, result); + if (lpBufferPointer.Pointer != IntPtr.Zero) + { + try + { + if (LocalFree(lpBufferPointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + SetLastError(lastError); + } + } + return result; + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ); + + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, int dwMessageId, int dwLanguageId, - [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpBuffer, - int nSize, - IntPtr Arguments + Span lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~(FORMAT_MESSAGE_ALLOCATE_BUFFER); + fixed (char* lpBufferPtr = lpBuffer) + fixed (IntPtr* pArguments = Arguments) + return FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBufferPtr, + lpBuffer.Length, + pArguments + ); + } + + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, SetLastError = true)] + private static extern unsafe int FormatMessageW( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [In] char* lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments ); + + /// + public static unsafe int FormatMessageW( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, + int dwMessageId, + int dwLanguageId, + out HLOCAL lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + fixed (IntPtr* pArguments = Arguments) + return FormatMessageW( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBuffer, + nSize, + pArguments + ); + } + #endregion + // C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\um\WinBase.h, line 2413 + #region FormatMessage function + // string lpSource +#pragma warning disable CS1572 // XML comment has a param tag, but there is no parameter by that name +#pragma warning disable CS1734 // XML comment has a paramref tag, but there is no parameter by that name /// - /// Formats a message string. The function requires a message definition as input. - /// It can come from a message table resource in an already-loaded module. - /// Or the caller can ask the function to search the system's message table resource(s) for the message definition. - /// The function finds the message definition in a message table resource based on a message identifier and a language identifier. - /// The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested. + /// Formats a message string. The function requires a message definition as input. The message definition can come from a buffer passed into the function. It can come from a message table resource in an already-loaded module. Or the caller can ask the function to search the system's message table resource(s) for the message definition. The function finds the message definition in a message table resource based on a message identifier and a language identifier. The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested. /// /// - /// /// The formatting options, and how to interpret the parameter. - /// The property specifies how the function handles line breaks in the output buffer. It can also specify the maximum width of a formatted output line. - /// + /// + /// ValueMeaning + /// + /// + /// Automatically set or cleared by the function as appropriate by the specific overload. + /// The function allocates a buffer large enough to hold the formatted message, and places the allocated buffer at the address specified by . The parameter specifies the minimum number of characters to allocate for an output message buffer. The caller should use the function to free the buffer when it is no longer needed. + /// If the length of the formatted message exceeds 128K bytes, then will fail and a subsequent call to will return . + /// In previous versions of Windows, this value was not available for use when compiling Windows Store apps. As of Windows 10 this value can be used. + /// Windows Server 2003 and Windows XP: If the length of the formatted message exceeds 128K bytes, then will not automatically fail with an error of . + /// + /// + /// Automatically applied by the function, as variadic argument invocation is not supported. + /// + /// + /// Automatically cleared by overloads that accept a , pointer or pointer value for the parameter. + /// The parameter is a module handle containing the message-table resource(s) to search. If this handle is , the current process's application image file will be searched. This flag cannot be used with . + /// If the module has no message table resource, the function fails with . + /// + /// + /// Automatically set by overloads that accept a , pointer or pointer value for the parameter. + /// The parameter is a value or a pointer to a null-terminated string that contains a message definition. The message definition may contain insert sequences, just as the message text in a message table resource may. This flag cannot be used with or . + /// + /// + /// Automatically cleared by overloads that accept a , pointer or pointer value for the parameter. + /// The function should search the system message-table resource(s) for the requested message. If this flag is specified with , the function searches the system message table if the message is not found in the module specified by . This flag cannot be used with . + /// If this flag is specified, an application can pass the result of the function to retrieve the message text for a system-defined error. + /// + /// + /// Insert sequences in the message definition are to be ignored and passed through to the output buffer unchanged. This flag is useful for fetching a message for later formatting. If this flag is set, the parameter is ignored. + /// + /// + /// + /// + /// Can be used to specify the maximum width of a formatted output line. + /// Valid values range from and including 0 (zero) up to but excluding 256 + /// (the range of the type). The Value is truncated to fit into this range. + /// If the value is a non-zero value other than , it specifies the maximum number of characters in an output line. The function ignores regular line breaks in the message definition text. The function never splits a string delimited by white space across a line break. The function stores hard-coded line breaks in the message definition text into the output buffer. Hard-coded line breaks are coded with the %n escape sequence. /// - /// The function ignores regular line breaks in the message definition text. The function never splits a string delimited by white space across a line break. The function stores hard-coded line breaks in the message definition text into the output buffer. Hard-coded line breaks are coded with the %n escape sequence. + /// The following values have special meaning. + /// + /// ValueMeaning + /// 0 (zero)There are no output line width restrictions. The function stores line breaks that are in the message definition text into the output buffer. + ///
(0x000000FF)
The function ignores regular line breaks in the message definition text. The function stores hard-coded line breaks in the message definition text into the output buffer. The function generates no new line breaks.
+ ///
///
/// /// /// The location of the message definition. The type of this parameter depends upon the settings in the parameter. /// - /// Meaning - /// A handle to the module that contains the message table to search. - /// Pointer to a string that consists of unformatted message text. It will be scanned for inserts and formatted accordingly. + /// SettingMeaning + /// A handle to the module that contains the message table to search. + /// Pointer or reference to a string that consists of unformatted message text. It will be scanned for inserts and formatted accordingly. /// /// If neither of these flags is set in , then is ignored. /// - /// The message identifier for the requested message. This parameter is ignored if includes . + /// The message identifier for the requested message. This parameter is ignored if includes . /// - /// The language identifier for the requested message. This parameter is ignored if includes . + /// The language identifier for the requested message. This parameter is ignored if includes . /// - /// If you pass a specific LCID in this parameter, will return a message for that LCID only. - /// If the function cannot find a message for that LCID, it sets Last-Error to . - /// If you pass in 0 (zero), looks for a message for LCIDs in the following order: + /// If you pass a specific LANGID in this parameter, will return a message for that LANGID only. If the function cannot find a message for that LANGID, it sets Last-Error to . If you pass in zero, looks for a message for LANGIDs in the following order: /// - /// Language neutral - /// Thread LCID, based on the thread's locale value - /// User default LCID, based on the user's default locale value - /// System default LCID, based on the system default locale value - /// US English + /// Language neutral + /// Thread LANGID, based on the thread's locale value + /// User default LANGID, based on the user's default locale value + /// System default LANGID, based on the system default locale value + /// US English /// /// - /// - /// If does not locate a message for any of the preceding LCIDs, - /// it returns any language message string that is present. If that fails, it returns . - /// + /// If does not locate a message for any of the preceding LANGIDs, it returns any language message string that is present. If that fails, it returns . /// /// - /// Receives a buffer to the null-terminated string that specifies the formatted message. ONLY use this overload if is included in . - /// The function allocates a buffer using the function. + /// A buffer that receives the null-terminated string that specifies the formatted message. If includes , the function allocates a buffer using the function, and places the pointer to the buffer at the address specified in . /// This buffer cannot be larger than 64K bytes. /// /// - /// Specifies the minimum number of characters to allocate for the output buffer to return in . + /// If the flag is not set, this parameter specifies the size of the output buffer, in characters. If is set, this parameter specifies the minimum number of characters to allocate for an output buffer. /// The output buffer cannot be larger than 64K bytes. /// /// - /// A pointer to an array of values that are used as insert values in the formatted message. A %1 in the format string indicates the first value in the Arguments array; a %2 indicates the second argument; and so on. - /// The interpretation of each value depends on the formatting information associated with the insert in the message definition. The default is to treat each value as a pointer to a null-terminated unicode string. - /// DO NOT use this parameter if the flag is not set in ! - /// The default value of can be used to indicate no insert arguments. This should be used when the flag is set in + /// An array of values that are used as insert values in the formatted message. A %1 in the format string indicates the first value in the Arguments array; a %2 indicates the second argument; and so on. + /// The interpretation of each value depends on the formatting information associated with the insert in the message definition. The default is to treat each value as a pointer to a null-terminated string. + /// The function automatically applies the flag. /// /// - /// - /// If the function succeeds, the return value is the number of characters stored in the output buffer, excluding the terminating null character. - /// - /// If the function fails, the return value is 0 (zero). To get extended error information, call . + /// If the function succeeds, the return value is the number of characters or bytes stored in the output buffer, excluding the terminating null character. + /// If the function fails, the return value is zero. To get extended error information, call . /// /// /// - /// Within the message text, several escape sequences are supported for dynamically formatting the message. These escape sequences and their meanings are shown in the following tables. All escape sequences start with the percent character (%). + /// Within the message text, several escape sequences are supported for dynamically formatting the message. These escape sequences and their meanings are shown in the following tables. All escape sequences start with the percent character (%). /// /// Escape sequenceMeaning /// %0Terminates a message text line without a trailing new line character. This escape sequence can be used to build up long lines or to terminate the message itself without a trailing new line character. It is useful for prompt messages. - /// - /// %n!format string! - /// - /// Identifies an insert. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. For more information, see Format Specification Fields. + /// %n!format string! + /// Identifies an insert. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. For more information, see Format Specification Fields. /// The format string can include a width and precision specifier for strings and a width specifier for integers. Use an asterisk (*) to specify the width and precision. For example, %1!.*s! or %1!*u!. /// If you do not use the width and precision specifiers, the insert numbers correspond directly to the input arguments. For example, if the source string is "%1 %2 %1" and the input arguments are "Bill" and "Bob", the formatted output string is "Bill Bob Bill". /// However, if you use a width and precision specifier, the insert numbers do not correspond directly to the input arguments. For example, the insert numbers for the previous example could change to "%1!*.*s! %4 %5!*s!". - /// The next insert number is n + 2 if the previous format string contained one asterisk and is n + 3 if two asterisks were specified. - /// If you want to repeat "Bill", as in the previous example, the arguments must include "Bill" twice. For example, if the source string is "%1!*.*s! %4 %5!*s!", the arguments could be, 4, 2, "Bill", "Bob", 6, "Bill" (if using the flag). The formatted string would then be " Bi Bob Bill". - /// Repeating insert numbers when the source string contains width and precision specifiers may not yield the intended results. If you replaced %5 with %1, the function would try to print a string at address 6 (likely resulting in an access violation). - /// Floating-point format specifiers—e, E, f, and g—are not supported. The workaround is to pre-format floating-point number using the regular method and then insert that string. - /// Inserts that use the I64 prefix are treated as two 32-bit arguments. - /// - /// + /// For an arguments array, the next insert number is n+2 if the previous format string contained one asterisk and is n+3 if two asterisks were specified. + /// If you want to repeat "Bill", as in the previous example, the arguments must include "Bill" twice. For example, if the source string is "%1!*.*s! %4 %5!*s!", the arguments could be, 4, 2, Bill, Bob, 6, Bill (if using the flag). The formatted string would then be " Bi Bob Bill". + /// Repeating insert numbers when the source string contains width and precision specifiers may not yield the intended results. If you replaced %5 with %1, the function would try to print a string at address 6 (likely resulting in an access violation). + /// Floating-point format specifiers — e, E, f, and g — are not supported. The workaround is to use the function to format the floating-point number into a temporary buffer, then use that buffer as the insert string. + /// Inserts that use the I64 prefix are treated as two 32-bit arguments. They must be used before subsequent arguments are used. Note that it may be easier for you to use instead of this prefix. + /// /// - /// - /// /// Any other nondigit character following a percent character is formatted in the output message without the percent character. Following are some examples. /// /// Format stringResulting output @@ -838,199 +1305,518 @@ IntPtr Arguments /// %spaceA single space. This format string can be used to ensure the appropriate number of trailing spaces in a message text line. /// %.A single period. This format string can be used to include a single period at the beginning of a line without terminating the message text definition. /// %!A single exclamation point. This format string can be used to include an exclamation point immediately after an insert without its being mistaken for the beginning of a format string. - /// %nA hard line break when the format string occurs at the end of a line. This format string is useful when is supplying regular line breaks so the message fits in a certain width. + /// %nA hard line break when the format string occurs at the end of a line. This format string is useful when is supplying regular line breaks so the message fits in a certain width. /// %rA hard carriage return without a trailing newline character. /// %tA single tab. /// /// - /// - /// Security Remarks
+ /// /// If this function is called without , the parameter must contain enough parameters to satisfy all insertion sequences in the message string, and they must be of the correct type. Therefore, do not use untrusted or unknown message strings with inserts enabled because they can contain more insertion sequences than provides, or those that may be of the wrong type. In particular, it is unsafe to take an arbitrary system error code returned from an API and use without . - /// + ///
/// /// /// Requirements - /// Minimum supported client:Windows XP [desktop apps | UWP apps] - /// Minimum supported server:Windows Server 2003 [desktop apps | UWP apps] + /// Minimum supported client:Windows XP [desktop apps| UWP apps] + /// Minimum supported server:Windows Server 2003 [desktop apps| UWP apps] /// /// - /// Microsoft Docs page: FormatMessageW function + /// Microsoft Docs page: FormatMessageW function ///
/// The native library containg the function could not be found. /// Unable to find the entry point for the function in the native library. + /// Error Handling Functions + /// Message Compiler + /// Message Tables +#pragma warning restore CS1572 // XML comment has a param tag, but there is no parameter by that name +#pragma warning restore CS1734 // XML comment has a paramref tag, but there is no parameter by that name public static unsafe int FormatMessage( - FORMAT_MESSAGE_OPTIONS dwFlags, - IntPtr lpSource, + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + string lpSource, int dwMessageId, int dwLanguageId, - out LPWSTR lpBuffer, - int nSize, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, out lpBuffer, nSize, Arguments); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageA), SetLastError = true)] - public static unsafe extern int FormatMessageA( - FORMAT_MESSAGE_OPTIONS dwFlags, - IntPtr lpSource, + StringBuilder lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + fixed (IntPtr* pArguments = Arguments) + return FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBuffer, + lpBuffer?.Capacity ?? 0, + pArguments + ); + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] string lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [Out] StringBuilder lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, lpSource, dwMessageId, + dwLanguageId, lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + /// + public static unsafe int FormatMessage( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + string lpSource, int dwMessageId, int dwLanguageId, - out byte* lpBuffer, - int nSize, - IntPtr Arguments - ); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageW), SetLastError = true)] - public static unsafe extern int FormatMessageW( - FORMAT_MESSAGE_OPTIONS dwFlags, + out string lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + + HLOCAL lpBufferPointer; + int result; + fixed (IntPtr* pArguments = Arguments) + result = FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBufferPointer, + nSize, + pArguments + ); + int lastError = Marshal.GetLastWin32Error(); + lpBuffer = lpBufferPointer.Pointer.MarshalAsAutoString(result); + if (lpBufferPointer.Pointer != IntPtr.Zero) + { + try + { + if (LocalFree(lpBufferPointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + SetLastError(lastError); + } + } + return result; + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] string lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, out lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, lpSource, dwMessageId, + dwLanguageId, out lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + /// + public static unsafe int FormatMessage( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + byte* lpSource, + int dwMessageId, + int dwLanguageId, + Span lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + fixed (byte* lpBufferPtr = lpBuffer) + fixed (IntPtr* pArguments = Arguments) + return FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBufferPtr, + lpBuffer.Length / Marshal.SystemDefaultCharSize, + pArguments + ); + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] byte* lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [In] byte* lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, (char*)lpSource, dwMessageId, + dwLanguageId, (char*)lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + /// + public static unsafe int FormatMessage( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + byte* lpSource, + int dwMessageId, + int dwLanguageId, + out HLOCAL lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_STRING | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~( + FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_FROM_SYSTEM + ); + fixed (IntPtr* pArguments = Arguments) + return FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBuffer, + nSize, + pArguments + ); + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] byte* lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, out lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, (char*)lpSource, dwMessageId, + dwLanguageId, out lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + // IntPtr lpSource + /// + public static unsafe int FormatMessage( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, IntPtr lpSource, int dwMessageId, int dwLanguageId, - out LPWSTR lpBuffer, - int nSize, - IntPtr Arguments - ); - /// - /// Formats a message string. The function requires a message definition as input. - /// It can come from a message table resource in an already-loaded module. - /// Or the caller can ask the function to search the system's message table resource(s) for the message definition. - /// The function finds the message definition in a message table resource based on a message identifier and a language identifier. - /// The function copies the formatted message text to an output buffer, processing any embedded insert sequences if requested. - /// - /// - /// - /// The formatting options, and how to interpret the parameter. - /// The property specifies how the function handles line breaks in the output buffer. It can also specify the maximum width of a formatted output line. - /// - /// - /// The function ignores regular line breaks in the message definition text. The function never splits a string delimited by white space across a line break. The function stores hard-coded line breaks in the message definition text into the output buffer. Hard-coded line breaks are coded with the %n escape sequence. - /// - /// - /// - /// A string that consists of unformatted message text. It will be scanned for inserts and formatted accordingly. - /// Only use this overload if the flag is set in . Otherwise, set this parameter to . - /// - /// The message identifier for the requested message. This parameter is ignored if includes . - /// - /// The language identifier for the requested message. This parameter is ignored if includes . - /// - /// If you pass a specific LCID in this parameter, will return a message for that LCID only. - /// If the function cannot find a message for that LCID, it sets Last-Error to . - /// If you pass in 0 (zero), looks for a message for LCIDs in the following order: - /// - /// Language neutral - /// Thread LCID, based on the thread's locale value - /// User default LCID, based on the user's default locale value - /// System default LCID, based on the system default locale value - /// US English - /// - /// - /// - /// If does not locate a message for any of the preceding LCIDs, - /// it returns any language message string that is present. If that fails, it returns . - /// - /// - /// - /// Receives a buffer to the null-terminated string that specifies the formatted message. ONLY use this overload if is included in . - /// The function allocates a buffer using the function. - /// This buffer cannot be larger than 64K bytes. - /// - /// - /// Specifies the minimum number of characters to allocate for the output buffer to return in . - /// The output buffer cannot be larger than 64K bytes. - /// - /// - /// A pointer to an array of values that are used as insert values in the formatted message. A %1 in the format string indicates the first value in the Arguments array; a %2 indicates the second argument; and so on. - /// The interpretation of each value depends on the formatting information associated with the insert in the message definition. The default is to treat each value as a pointer to a null-terminated unicode string. - /// DO NOT use this parameter if the flag is not set in ! - /// The default value of can be used to indicate no insert arguments. This should be used when the flag is set in - /// - /// - /// - /// If the function succeeds, the return value is the number of characters stored in the output buffer, excluding the terminating null character. - /// - /// If the function fails, the return value is 0 (zero). To get extended error information, call . - /// - /// - /// - /// Within the message text, several escape sequences are supported for dynamically formatting the message. These escape sequences and their meanings are shown in the following tables. All escape sequences start with the percent character (%). - /// - /// Escape sequenceMeaning - /// %0Terminates a message text line without a trailing new line character. This escape sequence can be used to build up long lines or to terminate the message itself without a trailing new line character. It is useful for prompt messages. - /// - /// %n!format string! - /// - /// Identifies an insert. The value of n can be in the range from 1 through 99. The format string (which must be surrounded by exclamation marks) is optional and defaults to !s! if not specified. For more information, see Format Specification Fields. - /// The format string can include a width and precision specifier for strings and a width specifier for integers. Use an asterisk (*) to specify the width and precision. For example, %1!.*s! or %1!*u!. - /// If you do not use the width and precision specifiers, the insert numbers correspond directly to the input arguments. For example, if the source string is "%1 %2 %1" and the input arguments are "Bill" and "Bob", the formatted output string is "Bill Bob Bill". - /// However, if you use a width and precision specifier, the insert numbers do not correspond directly to the input arguments. For example, the insert numbers for the previous example could change to "%1!*.*s! %4 %5!*s!". - /// The next insert number is n + 2 if the previous format string contained one asterisk and is n + 3 if two asterisks were specified. - /// If you want to repeat "Bill", as in the previous example, the arguments must include "Bill" twice. For example, if the source string is "%1!*.*s! %4 %5!*s!", the arguments could be, 4, 2, "Bill", "Bob", 6, "Bill" (if using the flag). The formatted string would then be " Bi Bob Bill". - /// Repeating insert numbers when the source string contains width and precision specifiers may not yield the intended results. If you replaced %5 with %1, the function would try to print a string at address 6 (likely resulting in an access violation). - /// Floating-point format specifiers—e, E, f, and g—are not supported. The workaround is to pre-format floating-point number using the regular method and then insert that string. - /// Inserts that use the I64 prefix are treated as two 32-bit arguments. - /// - /// - /// - /// - /// - /// Any other nondigit character following a percent character is formatted in the output message without the percent character. Following are some examples. - /// - /// Format stringResulting output - /// %%A single percent sign. - /// %spaceA single space. This format string can be used to ensure the appropriate number of trailing spaces in a message text line. - /// %.A single period. This format string can be used to include a single period at the beginning of a line without terminating the message text definition. - /// %!A single exclamation point. This format string can be used to include an exclamation point immediately after an insert without its being mistaken for the beginning of a format string. - /// %nA hard line break when the format string occurs at the end of a line. This format string is useful when is supplying regular line breaks so the message fits in a certain width. - /// %rA hard carriage return without a trailing newline character. - /// %tA single tab. - /// - /// - /// - /// Security Remarks
- /// If this function is called without , the parameter must contain enough parameters to satisfy all insertion sequences in the message string, and they must be of the correct type. Therefore, do not use untrusted or unknown message strings with inserts enabled because they can contain more insertion sequences than provides, or those that may be of the wrong type. In particular, it is unsafe to take an arbitrary system error code returned from an API and use without . - ///
- /// - /// - /// Requirements - /// Minimum supported client:Windows XP [desktop apps | UWP apps] - /// Minimum supported server:Windows Server 2003 [desktop apps | UWP apps] - /// - /// - /// Microsoft Docs page: FormatMessageW function - ///
- /// The native library containg the function could not be found. - /// Unable to find the entry point for the function in the native library. + StringBuilder lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~(FORMAT_MESSAGE_ALLOCATE_BUFFER); + fixed (IntPtr* pArguments = Arguments) + return FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBuffer, + lpBuffer?.Capacity ?? 0, + pArguments + ); + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [Out] StringBuilder lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, lpSource, dwMessageId, + dwLanguageId, lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + /// public static unsafe int FormatMessage( - FORMAT_MESSAGE_OPTIONS dwFlags, - string lpSource, + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, int dwMessageId, int dwLanguageId, - out LPWSTR lpBuffer, - int nSize, - IntPtr Arguments - ) => FormatMessageW(dwFlags, lpSource, dwMessageId, dwLanguageId, out lpBuffer, nSize, Arguments); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageA), SetLastError = true)] - public static unsafe extern int FormatMessageA( - FORMAT_MESSAGE_OPTIONS dwFlags, - [MarshalAs(UnmanagedType.LPStr)] string lpSource, + out string lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + + HLOCAL lpBufferPointer; + int result; + fixed (IntPtr* pArguments = Arguments) + result = FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBufferPointer, + nSize, + pArguments + ); + int lastError = Marshal.GetLastWin32Error(); + lpBuffer = lpBufferPointer.Pointer.MarshalAsAutoString(result); + if (lpBufferPointer.Pointer != IntPtr.Zero) + { + try + { + if (LocalFree(lpBufferPointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + finally + { + SetLastError(lastError); + } + } + return result; + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + out HLOCAL lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, out lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, lpSource, dwMessageId, + dwLanguageId, out lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + /// + public static unsafe int FormatMessage( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, int dwMessageId, int dwLanguageId, - out byte* lpBuffer, - int nSize, - IntPtr Arguments - ); - /// - [DllImport(NativeLibraryNames.Kernel32, CallingConvention = Winapi, EntryPoint = nameof(FormatMessageW), SetLastError = true)] - public static unsafe extern int FormatMessageW( - FORMAT_MESSAGE_OPTIONS dwFlags, - [MarshalAs(UnmanagedType.LPWStr)] string lpSource, + Span lpBuffer, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags &= ~(FORMAT_MESSAGE_ALLOCATE_BUFFER); + fixed (byte* lpBufferPtr = lpBuffer) + fixed (IntPtr* pArguments = Arguments) + return FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + lpBufferPtr, + lpBuffer.Length / Marshal.SystemDefaultCharSize, + pArguments + ); + } + +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + [DllImport(Kernel32, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + private static extern unsafe +#else + private static unsafe +#endif + int FormatMessage( + [In, MarshalAs(UnmanagedType.I4)] + FORMAT_MESSAGE_FLAGS dwFlags, + [In, Optional] IntPtr lpSource, + [In] int dwMessageId, + [In] int dwLanguageId, + [In] byte* lpBuffer, + [In] int nSize, + [In, Optional] IntPtr* Arguments + ) +#if !NETSTANDARD1_3 && !NETSTANDARD1_6 + ; +#else + => Marshal.SystemDefaultCharSize switch + { + 1 => FormatMessageA(dwFlags, lpSource, dwMessageId, + dwLanguageId, lpBuffer, nSize, Arguments), + 2 => FormatMessageW(dwFlags, lpSource, dwMessageId, + dwLanguageId, (char*)lpBuffer, nSize, Arguments), + _ => throw new PlatformNotSupportedException() + }; +#endif + + /// + public static unsafe int FormatMessage( + FORMAT_MESSAGE_FLAGS dwFlags, + int dwWidth, + IntPtr lpSource, int dwMessageId, int dwLanguageId, - out LPWSTR lpBuffer, - int nSize, - IntPtr Arguments - ); - #endregion + out HLOCAL lpBuffer, + int nSize = 0, + ReadOnlySpan Arguments = default + ) + { + dwFlags &= (FORMAT_MESSAGE_FLAGS)~(WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + dwFlags |= + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + (FORMAT_MESSAGE_FLAGS)(dwWidth & WinBaseConstants.FORMAT_MESSAGE_MAX_WIDTH_MASK); + fixed (IntPtr* pArguments = Arguments) + return FormatMessage( + dwFlags, + lpSource, + dwMessageId, + dwLanguageId, + out lpBuffer, + nSize, + pArguments + ); + } +#endregion } } diff --git a/test/THNETII.WinApi.Native.Test/Metadata/WinApiStructures.cs b/test/THNETII.WinApi.Native.Test/Metadata/WinApiStructures.cs index e5f46d3b..57d5beeb 100644 --- a/test/THNETII.WinApi.Native.Test/Metadata/WinApiStructures.cs +++ b/test/THNETII.WinApi.Native.Test/Metadata/WinApiStructures.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; + using THNETII.WinApi.Native.MinWinBase; using THNETII.WinApi.Native.MinWinDef; -using THNETII.WinApi.Native.WinBase; using THNETII.WinApi.Native.WinNT; using THNETII.WinApi.Native.WinUser; @@ -18,7 +18,6 @@ public static class WinApiStructures // MinWinDef typeof(HLOCAL), // WinBase - typeof(FORMAT_MESSAGE_OPTIONS), // WinNT typeof(ACCESS_MASK), typeof(ACCESS_REASON), diff --git a/test/THNETII.WinApi.Native.Test/THNETII.WinApi.Native.Test.csproj b/test/THNETII.WinApi.Native.Test/THNETII.WinApi.Native.Test.csproj index 44628b35..8eb31f3e 100644 --- a/test/THNETII.WinApi.Native.Test/THNETII.WinApi.Native.Test.csproj +++ b/test/THNETII.WinApi.Native.Test/THNETII.WinApi.Native.Test.csproj @@ -1,8 +1,8 @@ - + - 7.3 + 8 true netcoreapp3.1 THNETII.WinApi.Native @@ -18,10 +18,15 @@ - - + + + + + + + @@ -33,4 +38,9 @@ + + + + + diff --git a/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessage.cs b/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessage.cs new file mode 100644 index 00000000..96c73c0c --- /dev/null +++ b/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessage.cs @@ -0,0 +1,349 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +using THNETII.InteropServices.Memory; +using THNETII.WinApi.Native.MinWinDef; +using THNETII.WinApi.Native.WinError; + +using Xunit; + +namespace THNETII.WinApi.Native.WinBase.Test +{ + using static WinBaseFunctions; + using static WinErrorConstants; + + public static class FormatMessage + { + [FactWindowsOS] + public static void Call_FormatString_StringBuilder() + { + const string formatString = "%1!*.*s! %4 %5!*s!"; + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAuto("Bill"), + Marshal.StringToCoTaskMemAuto("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength; + StringBuilder messageBuilder = new StringBuilder(); + do + { + messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: formatString, + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: messageBuilder, + Arguments: arguments + ); + } while (messageLength == 0 && Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => + (messageBuilder.Capacity += 32) > 0, + int e => throw new Win32Exception(e) + }); + + string message = messageBuilder.ToString(0, messageLength); + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static void Call_FormatString_StringOut() + { + const string formatString = "%1!*.*s! %4 %5!*s!"; + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAuto("Bill"), + Marshal.StringToCoTaskMemAuto("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: formatString, + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: out string message, + nSize: default, + arguments + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static unsafe void Call_FormatString_Span() + { + IntPtr formatString = Marshal.StringToCoTaskMemAuto("%1!*.*s! %4 %5!*s!"); + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAuto("Bill"), + Marshal.StringToCoTaskMemAuto("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + string message = null; + for (int bufferLength = 16; message is null; bufferLength += 16) + { + Span messageBuffer = stackalloc byte[bufferLength * Marshal.SystemDefaultCharSize]; + int messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: (byte*)formatString.ToPointer(), + dwMessageId: default, + dwLanguageId: default, + messageBuffer, + arguments + ); + + switch (messageLength) + { + case 0: + _ = Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => 0, + int e => throw new Win32Exception(e), + }; + break; + default: + unsafe + { + fixed (byte* messagePointer = messageBuffer) + message = Marshal.PtrToStringAuto( + (IntPtr)messagePointer, messageLength); + } + break; + } + } + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(formatString); + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static unsafe void Call_FormatString_SpanOut() + { + IntPtr formatString = Marshal.StringToCoTaskMemAuto("%1!*.*s! %4 %5!*s!"); + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAuto("Bill"), + Marshal.StringToCoTaskMemAuto("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: (byte*)formatString.ToPointer(), + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: out HLOCAL messageBuffer, + nSize: default, + arguments + ); + + string message = messageBuffer.Pointer.MarshalAsAutoString(messageLength); + if (LocalFree(messageBuffer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(formatString); + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_StringBuilder(int errorcode) + { + StringBuilder messageBuilder = new StringBuilder(); + + int messageLength; + do + { + messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + lpBuffer: messageBuilder + ); + } while (messageLength == 0 && Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => + (messageBuilder.Capacity += 32) > 0, + int e => throw new Win32Exception(e) + }); + + string message = messageBuilder.ToString(0, messageLength); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_StringOut(int errorcode) + { + int messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + out string message + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_Span(int errorcode) + { + string message = null; + for (int bufferLength = 16; message is null; bufferLength += 16) + { + Span messageBuffer = stackalloc byte[bufferLength * Marshal.SystemDefaultCharSize]; + int messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + messageBuffer + ); + + switch (messageLength) + { + case 0: + _ = Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => 0, + int e => throw new Win32Exception(e), + }; + break; + default: + unsafe + { + fixed (byte* messagePointer = messageBuffer) + message = Marshal.PtrToStringAuto( + (IntPtr)messagePointer, messageLength); + } + break; + } + } + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_SpanOut(int errorcode) + { + int messageLength = FormatMessage( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + out HLOCAL messagePointer + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + string message = messagePointer.Pointer + .MarshalAsAutoString(messageLength); + if (LocalFree(messagePointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + } +} diff --git a/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessageA.cs b/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessageA.cs new file mode 100644 index 00000000..da2521bf --- /dev/null +++ b/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessageA.cs @@ -0,0 +1,349 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +using THNETII.InteropServices.Memory; +using THNETII.WinApi.Native.MinWinDef; +using THNETII.WinApi.Native.WinError; + +using Xunit; + +namespace THNETII.WinApi.Native.WinBase.Test +{ + using static WinBaseFunctions; + using static WinErrorConstants; + + public static class FormatMessageA + { + [FactWindowsOS] + public static void Call_FormatString_StringBuilder() + { + const string formatString = "%1!*.*s! %4 %5!*s!"; + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAnsi("Bill"), + Marshal.StringToCoTaskMemAnsi("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength; + StringBuilder messageBuilder = new StringBuilder(); + do + { + messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: formatString, + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: messageBuilder, + Arguments: arguments + ); + } while (messageLength == 0 && Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => + (messageBuilder.Capacity += 32) > 0, + int e => throw new Win32Exception(e) + }); + + string message = messageBuilder.ToString(0, messageLength); + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static void Call_FormatString_StringOut() + { + const string formatString = "%1!*.*s! %4 %5!*s!"; + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAnsi("Bill"), + Marshal.StringToCoTaskMemAnsi("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: formatString, + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: out string message, + nSize: default, + arguments + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static unsafe void Call_FormatString_Span() + { + IntPtr formatString = Marshal.StringToCoTaskMemAnsi("%1!*.*s! %4 %5!*s!"); + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAnsi("Bill"), + Marshal.StringToCoTaskMemAnsi("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + string message = null; + for (int bufferLength = 16; message is null; bufferLength += 16) + { + Span messageBuffer = stackalloc byte[bufferLength]; + int messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: (byte*)formatString.ToPointer(), + dwMessageId: default, + dwLanguageId: default, + messageBuffer, + arguments + ); + + switch (messageLength) + { + case 0: + _ = Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => 0, + int e => throw new Win32Exception(e), + }; + break; + default: + unsafe + { + fixed (byte* messagePointer = messageBuffer) + message = Marshal.PtrToStringAnsi( + (IntPtr)messagePointer, messageLength); + } + break; + } + } + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(formatString); + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static unsafe void Call_FormatString_SpanOut() + { + IntPtr formatString = Marshal.StringToCoTaskMemAnsi("%1!*.*s! %4 %5!*s!"); + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemAnsi("Bill"), + Marshal.StringToCoTaskMemAnsi("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: (byte*)formatString.ToPointer(), + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: out HLOCAL messageBuffer, + nSize: default, + arguments + ); + + string message = messageBuffer.Pointer.MarshalAsAnsiString(messageLength); + if (LocalFree(messageBuffer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(formatString); + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_StringBuilder(int errorcode) + { + StringBuilder messageBuilder = new StringBuilder(); + + int messageLength; + do + { + messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + lpBuffer: messageBuilder + ); + } while (messageLength == 0 && Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => + (messageBuilder.Capacity += 32) > 0, + int e => throw new Win32Exception(e) + }); + + string message = messageBuilder.ToString(0, messageLength); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_StringOut(int errorcode) + { + int messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + out string message + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_Span(int errorcode) + { + string message = null; + for (int bufferLength = 16; message is null; bufferLength += 16) + { + Span messageBuffer = stackalloc byte[bufferLength]; + int messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + messageBuffer + ); + + switch (messageLength) + { + case 0: + _ = Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => 0, + int e => throw new Win32Exception(e), + }; + break; + default: + unsafe + { + fixed (byte* messagePointer = messageBuffer) + message = Marshal.PtrToStringAnsi( + (IntPtr)messagePointer, messageLength); + } + break; + } + } + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_SpanOut(int errorcode) + { + int messageLength = FormatMessageA( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + out HLOCAL messagePointer + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + string message = messagePointer.Pointer + .MarshalAsAnsiString(messageLength); + if (LocalFree(messagePointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + } +} diff --git a/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessageW.cs b/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessageW.cs new file mode 100644 index 00000000..77b91958 --- /dev/null +++ b/test/THNETII.WinApi.Native.Test/WinBase.Test/FormatMessageW.cs @@ -0,0 +1,349 @@ +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +using THNETII.InteropServices.Memory; +using THNETII.WinApi.Native.MinWinDef; +using THNETII.WinApi.Native.WinError; + +using Xunit; + +namespace THNETII.WinApi.Native.WinBase.Test +{ + using static WinBaseFunctions; + using static WinErrorConstants; + + public static class FormatMessageW + { + [FactWindowsOS] + public static void Call_FormatString_StringBuilder() + { + const string formatString = "%1!*.*s! %4 %5!*s!"; + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemUni("Bill"), + Marshal.StringToCoTaskMemUni("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength; + StringBuilder messageBuilder = new StringBuilder(); + do + { + messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: formatString, + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: messageBuilder, + Arguments: arguments + ); + } while (messageLength == 0 && Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => + (messageBuilder.Capacity += 32) > 0, + int e => throw new Win32Exception(e) + }); + + string message = messageBuilder.ToString(0, messageLength); + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static void Call_FormatString_StringOut() + { + const string formatString = "%1!*.*s! %4 %5!*s!"; + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemUni("Bill"), + Marshal.StringToCoTaskMemUni("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: formatString, + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: out string message, + nSize: default, + arguments + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static unsafe void Call_FormatString_Span() + { + IntPtr formatString = Marshal.StringToCoTaskMemUni("%1!*.*s! %4 %5!*s!"); + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemUni("Bill"), + Marshal.StringToCoTaskMemUni("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + string message = null; + for (int bufferLength = 16; message is null; bufferLength += 16) + { + Span messageBuffer = stackalloc char[bufferLength]; + int messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: (char*)formatString.ToPointer(), + dwMessageId: default, + dwLanguageId: default, + messageBuffer, + arguments + ); + + switch (messageLength) + { + case 0: + _ = Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => 0, + int e => throw new Win32Exception(e), + }; + break; + default: + unsafe + { + fixed (char* messagePointer = messageBuffer) + message = Marshal.PtrToStringUni( + (IntPtr)messagePointer, messageLength); + } + break; + } + } + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(formatString); + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [FactWindowsOS] + public static unsafe void Call_FormatString_SpanOut() + { + IntPtr formatString = Marshal.StringToCoTaskMemUni("%1!*.*s! %4 %5!*s!"); + Span arguments = stackalloc IntPtr[] + { + (IntPtr)4, + (IntPtr)2, + Marshal.StringToCoTaskMemUni("Bill"), + Marshal.StringToCoTaskMemUni("Bob"), + (IntPtr)6, + IntPtr.Zero + }; + arguments[5] = arguments[2]; + + try + { + int messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_STRING, + dwWidth: default, + lpSource: (char*)formatString.ToPointer(), + dwMessageId: 0, + dwLanguageId: default, + lpBuffer: out HLOCAL messageBuffer, + nSize: default, + arguments + ); + + string message = messageBuffer.Pointer.MarshalAsUnicodeString(messageLength); + if (LocalFree(messageBuffer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + + Assert.Equal(" Bi Bob Bill", message); + } + finally + { + Marshal.FreeCoTaskMem(formatString); + Marshal.FreeCoTaskMem(arguments[2]); + Marshal.FreeCoTaskMem(arguments[3]); + } + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_StringBuilder(int errorcode) + { + StringBuilder messageBuilder = new StringBuilder(); + + int messageLength; + do + { + messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + lpBuffer: messageBuilder + ); + } while (messageLength == 0 && Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => + (messageBuilder.Capacity += 32) > 0, + int e => throw new Win32Exception(e) + }); + + string message = messageBuilder.ToString(0, messageLength); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_StringOut(int errorcode) + { + int messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + out string message + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_Span(int errorcode) + { + string message = null; + for (int bufferLength = 16; message is null; bufferLength += 16) + { + Span messageBuffer = stackalloc char[bufferLength]; + int messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + messageBuffer + ); + + switch (messageLength) + { + case 0: + _ = Marshal.GetLastWin32Error() switch + { + ERROR_INSUFFICIENT_BUFFER => 0, + int e => throw new Win32Exception(e), + }; + break; + default: + unsafe + { + fixed (char* messagePointer = messageBuffer) + message = Marshal.PtrToStringUni( + (IntPtr)messagePointer, messageLength); + } + break; + } + } + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + + [TheoryWindowsOS] + [InlineData(ERROR_SUCCESS)] + [InlineData(ERROR_OUTOFMEMORY)] + [InlineData(ERROR_INVALID_PARAMETER)] + public static void Call_Win32Error_SpanOut(int errorcode) + { + int messageLength = FormatMessageW( + dwFlags: FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FLAGS.FORMAT_MESSAGE_IGNORE_INSERTS, + dwWidth: default, + lpSource: IntPtr.Zero, + dwMessageId: errorcode, + dwLanguageId: default, + out HLOCAL messagePointer + ); + + if (messageLength == 0) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + string message = messagePointer.Pointer + .MarshalAsUnicodeString(messageLength); + if (LocalFree(messagePointer).Pointer != IntPtr.Zero) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Assert.NotEmpty(message); + Assert.DoesNotContain("\0", message, StringComparison.Ordinal); + } + } +}