Skip to content

Commit

Permalink
Implement Marshal.Get(Last)PInvokeErrorMessage() APIs. (#70685)
Browse files Browse the repository at this point in the history
* Implement Marshal.GetPInvokeErrorMessage() and Marshal.GetLastPInvokeErrorMessage() APIs.

The behavior of this API is to return the same value as
new Win32Exception(error).Message, while avoiding the
instantiation of an exception.

Consume new API in places where possible.
  • Loading branch information
AaronRobinsonMSFT authored Jun 24, 2022
1 parent f3c63d8 commit 318018e
Show file tree
Hide file tree
Showing 17 changed files with 144 additions and 50 deletions.
19 changes: 13 additions & 6 deletions src/libraries/Common/src/System/IO/Win32Marshal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,22 @@ internal static Exception GetExceptionForWin32Error(int errorCode, string? path
return new OperationCanceledException();
case Interop.Errors.ERROR_INVALID_PARAMETER:
default:
string msg = string.IsNullOrEmpty(path)
? GetPInvokeErrorMessage(errorCode)
: $"{GetPInvokeErrorMessage(errorCode)} : '{path}'";
return new IOException(
string.IsNullOrEmpty(path) ? GetMessage(errorCode) : $"{GetMessage(errorCode)} : '{path}'",
msg,
MakeHRFromErrorCode(errorCode));
}

static string GetPInvokeErrorMessage(int errorCode)
{
#if NET7_0_OR_GREATER
return Marshal.GetPInvokeErrorMessage(errorCode);
#else
return Interop.Kernel32.GetMessage(errorCode);
#endif
}
}

/// <summary>
Expand Down Expand Up @@ -90,10 +102,5 @@ internal static int TryMakeWin32ErrorCodeFromHR(int hr)

return hr;
}

/// <summary>
/// Returns a string message for the specified Win32 error code.
/// </summary>
internal static string GetMessage(int errorCode) => Interop.Kernel32.GetMessage(errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ private string GetMainWindowTitle()
#if DEBUG
// We never used to throw here, want to surface possible mistakes on our part
int error = Marshal.GetLastWin32Error();
Debug.Assert(error == 0, $"Failed GetWindowTextLengthW(): { new Win32Exception(error).Message }");
Debug.Assert(error == 0, $"Failed GetWindowTextLengthW(): { Marshal.GetPInvokeErrorMessage(error) }");
#endif
return string.Empty;
}
Expand All @@ -222,7 +222,7 @@ private string GetMainWindowTitle()
{
// We never used to throw here, want to surface possible mistakes on our part
int error = Marshal.GetLastWin32Error();
Debug.Assert(error == 0, $"Failed GetWindowTextW(): { new Win32Exception(error).Message }");
Debug.Assert(error == 0, $"Failed GetWindowTextW(): { Marshal.GetPInvokeErrorMessage(error) }");
}
#endif
return title.Slice(0, length).ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1881,7 +1881,6 @@
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\Win32Exception.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebugProvider.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\RuntimeEventSourceHelper.Windows.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
Expand Down Expand Up @@ -2176,7 +2175,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\AppDomain.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffer.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ComponentModel\Win32Exception.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebugProvider.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.Unix.cs" />
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace System.ComponentModel
/// </summary>
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public partial class Win32Exception : ExternalException, ISerializable
public class Win32Exception : ExternalException, ISerializable
{
private const int E_FAIL = unchecked((int)0x80004005);

Expand All @@ -28,7 +28,7 @@ public Win32Exception() : this(Marshal.GetLastPInvokeError())
/// <summary>
/// Initializes a new instance of the <see cref='System.ComponentModel.Win32Exception'/> class with the specified error.
/// </summary>
public Win32Exception(int error) : this(error, GetErrorMessage(error))
public Win32Exception(int error) : this(error, Marshal.GetPInvokeErrorMessage(error))
{
}
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ private static void SetEnvironmentVariableCore(string variable, string? value)

case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
case Interop.Errors.ERROR_NO_SYSTEM_RESOURCES:
throw new OutOfMemoryException(Interop.Kernel32.GetMessage(errorCode));
throw new OutOfMemoryException(Marshal.GetPInvokeErrorMessage(errorCode));

default:
throw new ArgumentException(Interop.Kernel32.GetMessage(errorCode));
throw new ArgumentException(Marshal.GetPInvokeErrorMessage(errorCode));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public static string UserDomainName
// The docs don't call this out clearly, but experimenting shows that the error returned is the following.
if (error != Interop.Errors.ERROR_INSUFFICIENT_BUFFER)
{
throw new InvalidOperationException(Win32Marshal.GetMessage(error));
throw new InvalidOperationException(Marshal.GetPInvokeErrorMessage(error));
}

domainBuilder.EnsureCapacity((int)length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,5 +195,15 @@ public static void SetLastSystemError(int error)
{
Interop.Sys.SetErrNo(error);
}

/// <summary>
/// Gets the system error message for the supplied error code.
/// </summary>
/// <param name="error">The error code.</param>
/// <returns>The error message associated with <paramref name="error"/>.</returns>
public static string GetPInvokeErrorMessage(int error)
{
return Interop.Sys.StrError(error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,15 @@ public static void SetLastSystemError(int error)
{
Interop.Kernel32.SetLastError(error);
}

/// <summary>
/// Gets the system error message for the supplied error code.
/// </summary>
/// <param name="error">The error code.</param>
/// <returns>The error message associated with <paramref name="error"/>.</returns>
public static string GetPInvokeErrorMessage(int error)
{
return Interop.Kernel32.GetMessage(error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1296,5 +1296,14 @@ public static int GetLastWin32Error()
{
return GetLastPInvokeError();
}

/// <summary>
/// Gets the system error message for the last PInvoke error code.
/// </summary>
/// <returns>The error message associated with the last PInvoke error code.</returns>
public static string GetLastPInvokeErrorMessage()
{
return GetPInvokeErrorMessage(GetLastPInvokeError());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,8 @@ public static void FreeHGlobal(System.IntPtr hglobal) { }
public static int GetLastPInvokeError() { throw null; }
public static int GetLastSystemError() { throw null; }
public static int GetLastWin32Error() { throw null; }
public static string GetLastPInvokeErrorMessage() { throw null; }
public static string GetPInvokeErrorMessage(int error) { throw null; }
[System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public static void GetNativeVariantForObject(object? obj, System.IntPtr pDstNativeVariant) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<Compile Include="System\Runtime\InteropServices\Marshal\IsComObjectTests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\IsComObjectTests.Windows.cs" Condition="'$(TargetPlatformIdentifier)' == 'windows'" />
<Compile Include="System\Runtime\InteropServices\Marshal\LastErrorTests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\PInvokeErrorMessageTests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\ReleaseTests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\ReleaseComObjectTests.cs" />
<Compile Include="System\Runtime\InteropServices\Marshal\ReleaseComObjectTests.Windows.cs" Condition="'$(TargetPlatformIdentifier)' == 'windows'" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.ComponentModel;
using Xunit;

namespace System.Runtime.InteropServices.Tests
{
public class PInvokeErrorMessageTests
{
public static IEnumerable<object[]> GetErrorCode_TestData()
{
yield return new object[] { 0 };
yield return new object[] { 1 };

// errno values
yield return new object[] { 0x10002 };
yield return new object[] { 0x10003 };
yield return new object[] { 0x10014 };
yield return new object[] { 0x1001D };

// HRESULT values
yield return new object[] { unchecked((int)0x80004001) };
yield return new object[] { unchecked((int)0x80004005) };
yield return new object[] { unchecked((int)0x80070057) };
yield return new object[] { unchecked((int)0x8000FFFF) };
}

[Theory]
[MemberData(nameof(GetErrorCode_TestData))]
public void PInvokeErrorMessage_Returns_Win32Exception_Message(int error)
{
// The Win32Exception represents the canonical system exception on
// all platforms. The GetPInvokeErrorMessage API is about providing
// this message in a manner that avoid the instantiation of a Win32Exception
// instance and querying for the message.
string expected = new Win32Exception(error).Message;
Assert.Equal(expected, Marshal.GetPInvokeErrorMessage(error));
}

[Theory]
[MemberData(nameof(GetErrorCode_TestData))]
public void LastPInvokeErrorMessage_Returns_Correct_Message(int error)
{
string expected = Marshal.GetPInvokeErrorMessage(error);

Marshal.SetLastPInvokeError(error);
Assert.Equal(expected, Marshal.GetLastPInvokeErrorMessage());
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void PInvokeErrorMessage_Returns_UniqueMessage_Windows()
{
var err1 = Marshal.GetPInvokeErrorMessage(unchecked((int)0x80070057)); // E_INVALIDARG
var err2 = Marshal.GetPInvokeErrorMessage(unchecked((int)0x80004001)); // E_NOTIMPL
Assert.NotNull(err1);
Assert.NotNull(err2);
Assert.NotEqual(err1, err2);
}

[Fact]
[PlatformSpecific(TestPlatforms.AnyUnix)]
public void PInvokeErrorMessage_Returns_UniqueMessage_Unix()
{
var err1 = Marshal.GetPInvokeErrorMessage(0x10002); // EACCES
var err2 = Marshal.GetPInvokeErrorMessage(0x10014); // EEXIST
Assert.NotNull(err1);
Assert.NotNull(err2);
// It is difficult to determine which error codes do or do not have
// translations on non-Windows. For now, we will just confirm the
// message is non-null.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ public SecurityIdentifier(WellKnownSidType sidType, SecurityIdentifier? domainSi
if (error == Interop.Errors.ERROR_INVALID_PARAMETER)
{
#pragma warning disable CA2208 // Instantiate argument exceptions correctly, combination of arguments used
throw new ArgumentException(new Win32Exception(error).Message, "sidType/domainSid");
throw new ArgumentException(Marshal.GetPInvokeErrorMessage(error), "sidType/domainSid");
#pragma warning restore CS2208
}
else if (error != Interop.Errors.ERROR_SUCCESS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public WindowsIdentity(string sUserPrincipalName)
unsafe
{
if (!Interop.Advapi32.AllocateLocallyUniqueId(&sourceContext.SourceIdentifier))
throw new SecurityException(new Win32Exception().Message);
throw new SecurityException(Marshal.GetLastPInvokeErrorMessage());

sourceName.AsSpan().CopyTo(new Span<byte>(sourceContext.SourceName, TOKEN_SOURCE.TOKEN_SOURCE_LENGTH));
}
Expand Down Expand Up @@ -264,7 +264,7 @@ private static SafeAccessTokenHandle DuplicateAccessToken(IntPtr accessToken)
true,
Interop.DuplicateHandleOptions.DUPLICATE_SAME_ACCESS))
{
throw new SecurityException(new Win32Exception().Message);
throw new SecurityException(Marshal.GetLastPInvokeErrorMessage());
}

return duplicateAccessToken;
Expand Down Expand Up @@ -465,15 +465,15 @@ private bool CheckNtTokenForSid(SecurityIdentifier sid)
(uint)TokenImpersonationLevel.Identification,
(uint)TokenType.TokenImpersonation,
ref token))
throw new SecurityException(new Win32Exception().Message);
throw new SecurityException(Marshal.GetLastPInvokeErrorMessage());
}


// CheckTokenMembership will check if the SID is both present and enabled in the access token.
if (!Interop.Advapi32.CheckTokenMembership((til != TokenImpersonationLevel.None ? _safeTokenHandle : token),
sid.BinaryForm,
ref isMember))
throw new SecurityException(new Win32Exception().Message);
throw new SecurityException(Marshal.GetLastPInvokeErrorMessage());


}
Expand Down Expand Up @@ -743,7 +743,7 @@ private static void RunImpersonatedInternal(SafeAccessTokenHandle token, Action

SafeAccessTokenHandle previousToken = GetCurrentToken(TokenAccessLevels.MaximumAllowed, false, out bool isImpersonating, out int hr);
if (previousToken == null || previousToken.IsInvalid)
throw new SecurityException(new Win32Exception(hr).Message);
throw new SecurityException(Marshal.GetPInvokeErrorMessage(hr));

s_currentImpersonatedToken.Value = isImpersonating ? previousToken : null;

Expand All @@ -756,7 +756,7 @@ private static void RunImpersonatedInternal(SafeAccessTokenHandle token, Action
delegate
{
if (!Interop.Advapi32.RevertToSelf())
Environment.FailFast(new Win32Exception().Message);
Environment.FailFast(Marshal.GetLastPInvokeErrorMessage());

s_currentImpersonatedToken.Value = null;

Expand All @@ -776,12 +776,12 @@ private static void CurrentImpersonatedTokenChanged(AsyncLocalValueChangedArgs<S
return; // we handle explicit Value property changes elsewhere.

if (!Interop.Advapi32.RevertToSelf())
Environment.FailFast(new Win32Exception().Message);
Environment.FailFast(Marshal.GetLastPInvokeErrorMessage());

if (args.CurrentValue != null && !args.CurrentValue.IsInvalid)
{
if (!Interop.Advapi32.ImpersonateLoggedOnUser(args.CurrentValue))
Environment.FailFast(new Win32Exception().Message);
Environment.FailFast(Marshal.GetLastPInvokeErrorMessage());
}
}

Expand All @@ -794,7 +794,7 @@ private static void CurrentImpersonatedTokenChanged(AsyncLocalValueChangedArgs<S
if (threadOnly && !isImpersonating)
return null;
// or there was an error
throw new SecurityException(new Win32Exception(hr).Message);
throw new SecurityException(Marshal.GetPInvokeErrorMessage(hr));
}
WindowsIdentity wi = new WindowsIdentity();
wi._safeTokenHandle.Dispose();
Expand Down Expand Up @@ -822,7 +822,7 @@ private static Exception GetExceptionFromNtStatus(int status)
return new OutOfMemoryException();

uint win32ErrorCode = Interop.Advapi32.LsaNtStatusToWinError((uint)status);
return new SecurityException(new Win32Exception(unchecked((int)win32ErrorCode)).Message);
return new SecurityException(Marshal.GetPInvokeErrorMessage((int)win32ErrorCode));
}

private static SafeAccessTokenHandle GetCurrentToken(TokenAccessLevels desiredAccess, bool threadOnly, out bool isImpersonating, out int hr)
Expand Down Expand Up @@ -899,7 +899,7 @@ private static Interop.LUID GetLogonAuthId(SafeAccessTokenHandle safeTokenHandle
dwLength,
out _);
if (!result)
throw new SecurityException(new Win32Exception().Message);
throw new SecurityException(Marshal.GetLastPInvokeErrorMessage());
break;
case Interop.Errors.ERROR_INVALID_HANDLE:
throw new ArgumentException(SR.Argument_InvalidImpersonationToken);
Expand All @@ -913,7 +913,7 @@ private static Interop.LUID GetLogonAuthId(SafeAccessTokenHandle safeTokenHandle
// Throw the exception.
goto default;
default:
throw new SecurityException(new Win32Exception(dwErrorCode).Message);
throw new SecurityException(Marshal.GetPInvokeErrorMessage(dwErrorCode));
}
return safeLocalAllocHandle;
}
Expand Down
Loading

0 comments on commit 318018e

Please sign in to comment.