Skip to content

Commit

Permalink
Merge pull request #145 from microsoft/fix119
Browse files Browse the repository at this point in the history
Declare HRESULT.ThrowOnFailure() method
  • Loading branch information
AArnott authored Feb 23, 2021
2 parents d529834 + b0898e1 commit c78d5d3
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 35 deletions.
63 changes: 38 additions & 25 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,7 @@ private static bool IsAnsiFunction(string methodName)
return false;
}

private static string FetchTemplate(string name)
private MemberDeclarationSyntax FetchTemplate(string name)
{
using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.templates.{name}");
if (templateStream is null)
Expand All @@ -1461,7 +1461,10 @@ private static string FetchTemplate(string name)
}

using StreamReader sr = new(templateStream);
return sr.ReadToEnd();
string template = sr.ReadToEnd();
MemberDeclarationSyntax member = ParseMemberDeclaration(template) ?? throw new GenerationFailedException($"Unable to parse a type from a template: {name}");
MemberDeclarationSyntax memberWithVisibility = this.ElevateVisibility(member);
return memberWithVisibility;
}

private FunctionPointerTypeSyntax FunctionPointer(CallingConvention callingConvention, MethodSignature<TypeSyntax> signature, string delegateName)
Expand Down Expand Up @@ -1680,7 +1683,7 @@ private bool IsCompilerGenerated(TypeDefinition typeDef)
{
MemberDeclarationSyntax? specialDeclaration = specialName switch
{
"PCWSTR" or "PCSTR" => ParseMemberDeclaration(FetchTemplate($"{specialName}.cs")),
"PCWSTR" or "PCSTR" => this.FetchTemplate($"{specialName}.cs"),
_ => throw new ArgumentException($"This special name is not recognized: \"{specialName}\".", nameof(specialName)),
};

Expand Down Expand Up @@ -2206,7 +2209,8 @@ private StructDeclarationSyntax CreateTypeDefStruct(TypeDefinition typeDef)
members = members.AddRange(this.CreateAdditionalTypeDefPWSTRMembers());
break;
case "HRESULT":
members = members.AddRange(this.CreateAdditionalTypeDefHRESULTMembers());
case "NTSTATUS":
members = members.AddRange(this.ExtractMembersFromTemplate(name));
break;
default:
break;
Expand All @@ -2229,6 +2233,36 @@ private StructDeclarationSyntax CreateTypeDefStruct(TypeDefinition typeDef)
return result;
}

private IEnumerable<MemberDeclarationSyntax> ExtractMembersFromTemplate(string name) => ((TypeDeclarationSyntax)this.FetchTemplate($"{name}.cs")).Members;

/// <summary>
/// Promotes an <see langword="internal" /> member to be <see langword="public"/> if <see cref="Visibility"/> indicates that generated APIs should be public.
/// This change is applied recursively.
/// </summary>
/// <param name="member">The member to potentially make public.</param>
/// <returns>The modified or original <paramref name="member"/>.</returns>
private MemberDeclarationSyntax ElevateVisibility(MemberDeclarationSyntax member)
{
if (this.Visibility == SyntaxKind.PublicKeyword)
{
int indexOfInternal = member.Modifiers.IndexOf(SyntaxKind.InternalKeyword);
if (indexOfInternal >= 0)
{
MemberDeclarationSyntax publicMember = member.WithModifiers(member.Modifiers.Replace(member.Modifiers[indexOfInternal], Token(this.Visibility)));

// Apply change recursively.
if (publicMember is TypeDeclarationSyntax memberContainer)
{
publicMember = memberContainer.WithMembers(List(memberContainer.Members.Select(this.ElevateVisibility)));
}

return publicMember;
}
}

return member;
}

private IEnumerable<MemberDeclarationSyntax> CreateAdditionalTypeDefBSTRMembers()
{
ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value"));
Expand Down Expand Up @@ -2332,27 +2366,6 @@ private IEnumerable<MemberDeclarationSyntax> CreateAdditionalTypeDefPWSTRMembers
#pragma warning restore SA1114 // Parameter list should follow declaration
}

private IEnumerable<MemberDeclarationSyntax> CreateAdditionalTypeDefHRESULTMembers()
{
ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value"));

// [DebuggerBrowsable(DebuggerBrowsableState.Never)]
// public bool Succeeded => this.Value >= 0;
yield return PropertyDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), "Succeeded")
.AddModifiers(Token(this.Visibility))
.WithExpressionBody(ArrowExpressionClause(BinaryExpression(SyntaxKind.GreaterThanOrEqualExpression, thisValue, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.AddAttributeLists(DebuggerBrowsableNever);

// [DebuggerBrowsable(DebuggerBrowsableState.Never)]
// public bool Failed => this.Value < 0;
yield return PropertyDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), "Failed")
.AddModifiers(Token(this.Visibility))
.WithExpressionBody(ArrowExpressionClause(BinaryExpression(SyntaxKind.LessThanExpression, thisValue, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0)))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
.AddAttributeLists(DebuggerBrowsableNever);
}

private StructDeclarationSyntax CreateTypeDefBOOLStruct(TypeDefinition typeDef)
{
const string name = "BOOL";
Expand Down
67 changes: 67 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/HRESULT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/// <summary>
/// Describes an HRESULT error or success condition.
/// </summary>
/// <remarks>
/// HRESULTs are 32 bit values layed out as follows:
/// <code>
/// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
/// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
/// +-+-+-+-+-+---------------------+-------------------------------+
/// |S|R|C|N|r| Facility | Code |
/// +-+-+-+-+-+---------------------+-------------------------------+
///
/// where
///
/// S - Severity - indicates success/fail
///
/// 0 - Success
/// 1 - Fail (COERROR)
///
/// R - reserved portion of the facility code, corresponds to NT's
/// second severity bit.
///
/// C - reserved portion of the facility code, corresponds to NT's
/// C field.
///
/// N - reserved portion of the facility code. Used to indicate a
/// mapped NT status value.
///
/// r - reserved portion of the facility code. Reserved for internal
/// use. Used to indicate HRESULT values that are not status
/// values, but are instead message ids for display strings.
///
/// Facility - is the facility code
///
/// Code - is the facility's status code
/// </code>
/// </remarks>
partial struct HRESULT
{
public static implicit operator uint(HRESULT value) => (uint)value.Value;
public static explicit operator HRESULT(uint value) => new HRESULT((int)value);

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal bool Succeeded => this.Value >= 0;

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal bool Failed => this.Value < 0;

/// <inheritdoc cref="Marshal.ThrowExceptionForHR(int, IntPtr)" />
/// <param name="errorInfo">
/// A pointer to the IErrorInfo interface that provides more information about the
/// error. You can specify <see cref="IntPtr.Zero"/> to use the current IErrorInfo interface, or
/// <c>new IntPtr(-1)</c> to ignore the current IErrorInfo interface and construct the exception
/// just from the error code.
/// </param>
/// <returns><see langword="this"/> <see cref="HRESULT"/>, if it does not reflect an error.</returns>
/// <seealso cref="Marshal.ThrowExceptionForHR(int, IntPtr)"/>
internal HRESULT ThrowOnFailure(IntPtr errorInfo = default)
{
Marshal.ThrowExceptionForHR(this.Value, errorInfo);
return this;
}

public override string ToString() => this.Value.ToString();

internal string ToString(string format, IFormatProvider formatProvider) => ((uint)this.Value).ToString(format, formatProvider);
}
40 changes: 40 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/NTSTATUS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// <summary>
/// Win32 return error codes.
/// </summary>
/// <remarks>
/// This values come from https://msdn.microsoft.com/en-us/library/cc704588.aspx
/// Values are 32 bit values laid out as follows:
/// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
/// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
/// +---+-+-+-----------------------+-------------------------------+
/// |Sev|C|R| Facility | Code |
/// +---+-+-+-----------------------+-------------------------------+
/// where
/// Sev - is the severity code
/// 00 - Success
/// 01 - Informational
/// 10 - Warning
/// 11 - Error
/// C - is the Customer code flag
/// R - is a reserved bit
/// Facility - is the facility code
/// Code - is the facility's status code
///
/// FacilityCodes 0x5 - 0xF have been allocated by various drivers.
/// The success status codes 0 - 63 are reserved for wait completion status.
/// </remarks>
partial struct NTSTATUS
{
public static implicit operator uint(NTSTATUS value) => (uint)value.Value;
public static explicit operator NTSTATUS(uint value) => new NTSTATUS((int)value);

internal Severity SeverityCode => (Severity)(((uint)this.Value & 0xc0000000) >> 30);

internal enum Severity
{
Success,
Informational,
Warning,
Error,
}
}
16 changes: 16 additions & 0 deletions test/GenerationSandbox.Tests/BasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,20 @@ public void HRESULT_Failed()
Assert.False(((HRESULT)1).Failed);
Assert.True(((HRESULT)(-1)).Failed);
}

[Fact]
public void NTSTATUS_Severity()
{
Assert.Equal(NTSTATUS.Severity.Success, ((NTSTATUS)0).SeverityCode);
Assert.Equal(NTSTATUS.Severity.Success, ((NTSTATUS)0x3fffffff).SeverityCode);

Assert.Equal(NTSTATUS.Severity.Informational, ((NTSTATUS)0x40000000).SeverityCode);
Assert.Equal(NTSTATUS.Severity.Informational, ((NTSTATUS)0x7fffffff).SeverityCode);

Assert.Equal(NTSTATUS.Severity.Warning, ((NTSTATUS)0x80000000).SeverityCode);
Assert.Equal(NTSTATUS.Severity.Warning, ((NTSTATUS)0xbfffffff).SeverityCode);

Assert.Equal(NTSTATUS.Severity.Error, ((NTSTATUS)0xc0000000).SeverityCode);
Assert.Equal(NTSTATUS.Severity.Error, ((NTSTATUS)0xffffffff).SeverityCode);
}
}
1 change: 1 addition & 0 deletions test/GenerationSandbox.Tests/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ CreateFile
DISPLAYCONFIG_VIDEO_SIGNAL_INFO
BOOL
HDC_UserSize
NTSTATUS
10 changes: 0 additions & 10 deletions test/SpellChecker/PInvoke.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,5 @@ internal static unsafe HRESULT CoCreateInstance<T>(in System.Guid rclsid, IUnkno
ppv = (T*)o;
return hr;
}

/// <inheritdoc cref="Marshal.ThrowExceptionForHR(int, IntPtr)" />
/// <returns>The value from <paramref name="errorCode"/> if it does not reflect an error.</returns>
/// <seealso cref="Marshal.ThrowExceptionForHR(int, IntPtr)"/>
/// <seealso href="https://github.com/microsoft/CsWin32/issues/119" />
internal static HRESULT ThrowOnFailure(this HRESULT errorCode, IntPtr errorInfo = default)
{
Marshal.ThrowExceptionForHR(errorCode, errorInfo);
return errorCode;
}
}
}

0 comments on commit c78d5d3

Please sign in to comment.