diff --git a/src/Tests/UnitTest/ExceptionTests.cs b/src/Tests/UnitTest/ExceptionTests.cs new file mode 100644 index 0000000000..43376ddfbc --- /dev/null +++ b/src/Tests/UnitTest/ExceptionTests.cs @@ -0,0 +1,24 @@ +using System; +using System.Globalization; +using WinRT; +using Xunit; + +namespace UnitTest +{ + public class ExceptionTests + { + [Fact] + public void TestGetExceptionForHR_WithValidHResult_ReturnsSystemFormattedException() + { + const int RPC_E_WRONG_THREAD = unchecked((int)0x8001010E); + + Exception exception = ExceptionHelpers.GetExceptionForHR(RPC_E_WRONG_THREAD); + Assert.NotNull(exception); + Assert.False(string.IsNullOrWhiteSpace(exception.Message)); + if (CultureInfo.CurrentUICulture.Name == "en-US") + { + Assert.Equal("The application called an interface that was marshalled for a different thread. (0x8001010E)", exception.Message); + } + } + } +} diff --git a/src/WinRT.Runtime/ExceptionHelpers.cs b/src/WinRT.Runtime/ExceptionHelpers.cs index b162deb1ae..4e1062fca6 100644 --- a/src/WinRT.Runtime/ExceptionHelpers.cs +++ b/src/WinRT.Runtime/ExceptionHelpers.cs @@ -227,6 +227,27 @@ private static Exception GetExceptionForHR(int hr, bool useGlobalErrorState, boo } } + if (string.IsNullOrWhiteSpace(errorMessage)) + { + char* message = default; + if (Platform.FormatMessageW(0x13FF /* FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK */, + null, + (uint)hr, + 0, + &message, + 0, + null) > 0) + { + errorMessage = $"{new string(message)}(0x{hr:X8})"; + + // LocalHandle isn't needed since FormatMessage uses LMEM_FIXED, + // and while we can use Marshal.FreeHGlobal since it uses LocalFree internally, + // it's not guranteed that this behavior stays the same in the future, + // especially considering the method's name, so it's safer to use LocalFree directly. + Platform.LocalFree(message); + } + } + switch (hr) { case E_CHANGED_STATE: @@ -348,7 +369,7 @@ private static Exception GetExceptionForHR(int hr, bool useGlobalErrorState, boo break; default: - ex = new COMException(errorMessage, hr); + ex = !string.IsNullOrEmpty(errorMessage) ? new COMException(errorMessage, hr) : new COMException($"0x{hr:X8}", hr); break; } diff --git a/src/WinRT.Runtime/Platform.cs b/src/WinRT.Runtime/Platform.cs index 8b54e7b083..d291b16c5c 100644 --- a/src/WinRT.Runtime/Platform.cs +++ b/src/WinRT.Runtime/Platform.cs @@ -48,6 +48,12 @@ public static extern unsafe int WindowsCreateStringReference( [DllImport("api-ms-win-core-com-l1-1-0.dll")] public static extern unsafe int CoCreateFreeThreadedMarshaler(IntPtr outer, IntPtr* marshalerPtr); + + [DllImport("kernel32.dll")] + public static extern unsafe uint FormatMessageW(uint dwFlags, void* lpSource, uint dwMessageId, uint dwLanguageId, char** lpBuffer, uint nSize, void* pArguments); + + [DllImport("kernel32.dll")] + public static extern unsafe void* LocalFree(void* hMem); } // Handcrafted P/Invoke with TFM-specific handling, or thin high-level abstractions (eg. 'TryGetProcAddress'/'GetProcAddress') diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 82dbd6cf54..b6f3e82b22 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -5883,7 +5883,7 @@ return (eventSource.Subscribe, eventSource.Unsubscribe); } } - std::string get_vmethod_delegate_type(writer& w, MethodDef const& method, std::string vmethod_name) + std::string get_vmethod_delegate_type(writer& w, MethodDef const& method, std::string) { method_signature signature{ method }; if (is_special(method))