Skip to content

Commit ae4b609

Browse files
authored
Merge pull request #666 from elachlan/HRESULT
Improvements to HRESULT
2 parents 65f78d0 + 5f2707b commit ae4b609

File tree

5 files changed

+180
-24
lines changed

5 files changed

+180
-24
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Create a `NativeMethods.txt` file in your project directory that lists the APIs
5252
Each line may consist of *one* of the following:
5353

5454
* Exported method name (e.g. `CreateFile`). This *may* include the `A` or `W` suffix, where applicable. This *may* be qualified with a namespace but is only recommended in cases of ambiguity, which CsWin32 will prompt where appropriate.
55+
* A macro name (e.g. `HRESULT_FROM_WIN32`). These are generated into the same class with extern methods. Macros must be hand-authored into CsWin32, so let us know if you want to see a macro added.
5556
* A namespace to generate all APIs from (e.g. `Windows.Win32.Storage.FileSystem` would search the metadata for all APIs within that namespace and generate them).
5657
* Module name followed by `.*` to generate all methods exported from that module (e.g. `Kernel32.*`).
5758
* The name of a struct, enum, constant or interface to generate. This *may* be qualified with a namespace but is only recommended in cases of ambiguity, which CsWin32 will prompt where appropriate.

src/Microsoft.Windows.CsWin32/Generator.cs

Lines changed: 140 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public class Generator : IDisposable
8282
private const string OriginalDelegateAnnotation = "OriginalDelegate";
8383

8484
private static readonly Dictionary<string, MethodDeclarationSyntax> PInvokeHelperMethods;
85+
private static readonly Dictionary<string, MethodDeclarationSyntax> PInvokeMacros;
8586

8687
private static readonly string AutoGeneratedHeader = @"// ------------------------------------------------------------------------------
8788
// <auto-generated>
@@ -98,6 +99,12 @@ public class Generator : IDisposable
9899
/// <content>
99100
/// Contains extern methods from ""{0}"".
100101
/// </content>
102+
".Replace("\r\n", "\n");
103+
104+
private static readonly string PartialPInvokeMacrosContentComment = @"
105+
/// <content>
106+
/// Contains macros.
107+
/// </content>
101108
".Replace("\r\n", "\n");
102109

103110
private static readonly SyntaxTriviaList InlineArrayUnsafeAsSpanComment = ParseLeadingTrivia(@"/// <summary>
@@ -348,6 +355,7 @@ public class Generator : IDisposable
348355
private readonly IdentifierNameSyntax methodsAndConstantsClassName;
349356
private readonly HashSet<string> injectedPInvokeHelperMethods = new();
350357
private readonly HashSet<string> injectedPInvokeHelperMethodsToFriendlyOverloadsExtensions = new();
358+
private readonly HashSet<string> injectedPInvokeMacros = new();
351359
private readonly Dictionary<TypeDefinitionHandle, bool> managedTypesCheck = new();
352360
private bool needsWinRTCustomMarshaler;
353361

@@ -359,6 +367,13 @@ static Generator()
359367
}
360368

361369
PInvokeHelperMethods = ((ClassDeclarationSyntax)member).Members.OfType<MethodDeclarationSyntax>().ToDictionary(m => m.Identifier.ValueText, m => m);
370+
371+
if (!TryFetchTemplate("PInvokeClassMacros", null, out member))
372+
{
373+
throw new GenerationFailedException("Missing embedded resource.");
374+
}
375+
376+
PInvokeMacros = ((ClassDeclarationSyntax)member).Members.OfType<MethodDeclarationSyntax>().ToDictionary(m => m.Identifier.ValueText, m => m);
362377
}
363378

364379
/// <summary>
@@ -477,34 +492,35 @@ private IEnumerable<MemberDeclarationSyntax> NamespaceMembers
477492
{
478493
IEnumerable<IGrouping<string, MemberDeclarationSyntax>> members = this.committedCode.MembersByModule;
479494
IEnumerable<MemberDeclarationSyntax> result = Enumerable.Empty<MemberDeclarationSyntax>();
480-
for (int i = 0; i < members.Count(); i++)
495+
int i = 0;
496+
foreach (IGrouping<string, MemberDeclarationSyntax> entry in members)
481497
{
482-
IGrouping<string, MemberDeclarationSyntax> entry = members.ElementAt(i);
483-
if (i == 0)
498+
ClassDeclarationSyntax partialClass = DeclarePInvokeClass(entry.Key)
499+
.AddMembers(entry.ToArray())
500+
.WithLeadingTrivia(ParseLeadingTrivia(string.Format(CultureInfo.InvariantCulture, PartialPInvokeContentComment, entry.Key)));
501+
if (i++ == 0)
484502
{
485-
result = result.Concat(new MemberDeclarationSyntax[]
486-
{
487-
ClassDeclaration(Identifier(this.options.ClassName))
488-
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.PartialKeyword))
489-
.AddMembers(entry.ToArray())
503+
partialClass = partialClass
504+
.WithoutLeadingTrivia()
490505
.AddAttributeLists(AttributeList().AddAttributes(GeneratedCodeAttribute))
491-
.WithLeadingTrivia(ParseLeadingTrivia(string.Format(CultureInfo.InvariantCulture, PartialPInvokeContentComment, entry.Key)))
492-
.WithAdditionalAnnotations(new SyntaxAnnotation(SimpleFileNameAnnotation, $"{this.options.ClassName}.{entry.Key}")),
493-
});
494-
}
495-
else
496-
{
497-
result = result.Concat(new MemberDeclarationSyntax[]
498-
{
499-
ClassDeclaration(Identifier(this.options.ClassName))
500-
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.PartialKeyword))
501-
.AddMembers(entry.ToArray())
502-
.WithLeadingTrivia(ParseLeadingTrivia(string.Format(CultureInfo.InvariantCulture, PartialPInvokeContentComment, entry.Key)))
503-
.WithAdditionalAnnotations(new SyntaxAnnotation(SimpleFileNameAnnotation, $"{this.options.ClassName}.{entry.Key}")),
504-
});
506+
.WithLeadingTrivia(partialClass.GetLeadingTrivia());
505507
}
508+
509+
result = result.Concat(new MemberDeclarationSyntax[] { partialClass });
506510
}
507511

512+
ClassDeclarationSyntax macrosPartialClass = DeclarePInvokeClass("Macros")
513+
.AddMembers(this.committedCode.Macros.ToArray())
514+
.WithLeadingTrivia(ParseLeadingTrivia(PartialPInvokeMacrosContentComment));
515+
if (macrosPartialClass.Members.Count > 0)
516+
{
517+
result = result.Concat(new MemberDeclarationSyntax[] { macrosPartialClass });
518+
}
519+
520+
ClassDeclarationSyntax DeclarePInvokeClass(string fileNameKey) => ClassDeclaration(Identifier(this.options.ClassName))
521+
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.PartialKeyword))
522+
.WithAdditionalAnnotations(new SyntaxAnnotation(SimpleFileNameAnnotation, $"{this.options.ClassName}.{fileNameKey}"));
523+
508524
result = result.Concat(this.committedCode.GeneratedTypes);
509525

510526
ClassDeclarationSyntax inlineArrayIndexerExtensionsClass = this.DeclareInlineArrayIndexerExtensionsClass();
@@ -574,6 +590,8 @@ public void GenerateAll(CancellationToken cancellationToken)
574590
this.RequestAllInteropTypes(cancellationToken);
575591

576592
this.GenerateAllConstants(cancellationToken);
593+
594+
this.GenerateAllMacros(cancellationToken);
577595
}
578596

579597
/// <inheritdoc cref="TryGenerate(string, out IReadOnlyList{string}, CancellationToken)"/>
@@ -645,6 +663,12 @@ public bool TryGenerate(string apiNameOrModuleWildcard, out IReadOnlyList<string
645663
return result;
646664
}
647665

666+
result = this.TryGenerateMacro(apiNameOrModuleWildcard, out preciseApi);
667+
if (result || preciseApi.Count > 1)
668+
{
669+
return result;
670+
}
671+
648672
return false;
649673
}
650674
}
@@ -767,6 +791,30 @@ public void GenerateAllConstants(CancellationToken cancellationToken)
767791
}
768792
}
769793

794+
/// <summary>
795+
/// Generates a projection of all macros.
796+
/// </summary>
797+
/// <param name="cancellationToken">A cancellation token.</param>
798+
public void GenerateAllMacros(CancellationToken cancellationToken)
799+
{
800+
foreach (KeyValuePair<string, MethodDeclarationSyntax> macro in PInvokeMacros)
801+
{
802+
cancellationToken.ThrowIfCancellationRequested();
803+
804+
try
805+
{
806+
this.volatileCode.GenerationTransaction(delegate
807+
{
808+
this.RequestMacro(macro.Value);
809+
});
810+
}
811+
catch (GenerationFailedException ex) when (IsPlatformCompatibleException(ex))
812+
{
813+
// Something transitively required for this field is not available for this platform, so skip this method.
814+
}
815+
}
816+
}
817+
770818
/// <summary>
771819
/// Generates all extern methods exported from a particular module, along with all their supporting types.
772820
/// </summary>
@@ -1038,6 +1086,34 @@ public bool TryGenerateConstant(string possiblyQualifiedName, out IReadOnlyList<
10381086
return false;
10391087
}
10401088

1089+
/// <summary>
1090+
/// Generate code for the named macro, if it is recognized.
1091+
/// </summary>
1092+
/// <param name="macroName">The name of the macro. Never qualified with a namespace.</param>
1093+
/// <param name="preciseApi">Receives the canonical API names that <paramref name="macroName"/> matched on.</param>
1094+
/// <returns><see langword="true"/> if a match was found and the macro generated; otherwise <see langword="false"/>.</returns>
1095+
public bool TryGenerateMacro(string macroName, out IReadOnlyList<string> preciseApi)
1096+
{
1097+
if (macroName is null)
1098+
{
1099+
throw new ArgumentNullException(nameof(macroName));
1100+
}
1101+
1102+
if (!PInvokeMacros.TryGetValue(macroName, out MethodDeclarationSyntax macro))
1103+
{
1104+
preciseApi = Array.Empty<string>();
1105+
return false;
1106+
}
1107+
1108+
this.volatileCode.GenerationTransaction(delegate
1109+
{
1110+
this.RequestMacro(macro);
1111+
});
1112+
1113+
preciseApi = ImmutableList.Create(macroName);
1114+
return true;
1115+
}
1116+
10411117
/// <summary>
10421118
/// Produces a sequence of suggested APIs with a similar name to the specified one.
10431119
/// </summary>
@@ -1525,6 +1601,24 @@ internal void RequestConstant(FieldDefinitionHandle fieldDefHandle)
15251601
});
15261602
}
15271603

1604+
internal void RequestMacro(MethodDeclarationSyntax macro)
1605+
{
1606+
this.volatileCode.GenerateMacro(macro.Identifier.ValueText, delegate
1607+
{
1608+
this.volatileCode.AddMacro(macro.Identifier.ValueText, (MethodDeclarationSyntax)this.ElevateVisibility(macro));
1609+
1610+
// Generate any additional types that this macro relies on.
1611+
foreach (QualifiedNameSyntax identifier in macro.DescendantNodes().OfType<QualifiedNameSyntax>())
1612+
{
1613+
string identifierString = identifier.ToString();
1614+
if (identifierString.StartsWith(GlobalNamespacePrefix, StringComparison.Ordinal))
1615+
{
1616+
this.TryGenerateType(identifierString.Substring(GlobalNamespacePrefix.Length));
1617+
}
1618+
}
1619+
});
1620+
}
1621+
15281622
internal TypeSyntax? RequestSafeHandle(string releaseMethod)
15291623
{
15301624
if (!this.options.UseSafeHandles)
@@ -5901,6 +5995,8 @@ private class GeneratedCode
59015995

59025996
private readonly Dictionary<string, (MemberDeclarationSyntax Type, bool TopLevel)> specialTypes = new(StringComparer.Ordinal);
59035997

5998+
private readonly Dictionary<string, MethodDeclarationSyntax> macros = new(StringComparer.Ordinal);
5999+
59046000
/// <summary>
59056001
/// The set of types that are or have been generated so we don't stack overflow for self-referencing types.
59066002
/// </summary>
@@ -5934,7 +6030,7 @@ internal GeneratedCode(GeneratedCode parent)
59346030
}
59356031

59366032
internal bool IsEmpty => this.modulesAndMembers.Count == 0 && this.types.Count == 0 && this.fieldsToSyntax.Count == 0 && this.safeHandleTypes.Count == 0 && this.specialTypes.Count == 0
5937-
&& this.inlineArrayIndexerExtensionsMembers.Count == 0 && this.comInterfaceFriendlyExtensionsMembers.Count == 0;
6033+
&& this.inlineArrayIndexerExtensionsMembers.Count == 0 && this.comInterfaceFriendlyExtensionsMembers.Count == 0 && this.macros.Count == 0;
59386034

59396035
internal IEnumerable<MemberDeclarationSyntax> GeneratedTypes => this.GetTypesWithInjectedFields()
59406036
.Concat(this.specialTypes.Values.Where(st => !st.TopLevel).Select(st => st.Type))
@@ -5961,6 +6057,8 @@ internal IEnumerable<IGrouping<string, MemberDeclarationSyntax>> MembersByModule
59616057
}
59626058
}
59636059

6060+
internal IEnumerable<MethodDeclarationSyntax> Macros => this.macros.Values;
6061+
59646062
internal void AddSafeHandleType(ClassDeclarationSyntax safeHandleDeclaration)
59656063
{
59666064
this.ThrowIfNotGenerating();
@@ -5998,6 +6096,12 @@ internal void AddConstant(FieldDefinitionHandle fieldDefHandle, FieldDeclaration
59986096
this.fieldsToSyntax.Add(fieldDefHandle, (constantDeclaration, fieldType));
59996097
}
60006098

6099+
internal void AddMacro(string macroName, MethodDeclarationSyntax macro)
6100+
{
6101+
this.ThrowIfNotGenerating();
6102+
this.macros.Add(macroName, macro);
6103+
}
6104+
60016105
internal void AddInlineArrayIndexerExtension(MethodDeclarationSyntax inlineIndexer)
60026106
{
60036107
this.ThrowIfNotGenerating();
@@ -6161,6 +6265,18 @@ internal void GenerateConstant(FieldDefinitionHandle fieldDefinitionHandle, Acti
61616265
generator();
61626266
}
61636267

6268+
internal void GenerateMacro(string macroName, Action generator)
6269+
{
6270+
this.ThrowIfNotGenerating();
6271+
6272+
if (this.macros.ContainsKey(macroName) || this.parent?.macros.ContainsKey(macroName) is true)
6273+
{
6274+
return;
6275+
}
6276+
6277+
generator();
6278+
}
6279+
61646280
internal bool TryGetSafeHandleForReleaseMethod(string releaseMethod, out TypeSyntax? safeHandleType)
61656281
{
61666282
return this.releaseMethodsWithSafeHandleTypesGenerating.TryGetValue(releaseMethod, out safeHandleType)
@@ -6219,6 +6335,7 @@ private void Commit(GeneratedCode? parent)
62196335
Commit(this.safeHandleTypes, parent?.safeHandleTypes);
62206336
Commit(this.specialTypes, parent?.specialTypes);
62216337
Commit(this.typesGenerating, parent?.typesGenerating);
6338+
Commit(this.macros, parent?.macros);
62226339
Commit(this.methodsGenerating, parent?.methodsGenerating);
62236340
Commit(this.specialTypesGenerating, parent?.specialTypesGenerating);
62246341
Commit(this.releaseMethodsWithSafeHandleTypesGenerating, parent?.releaseMethodsWithSafeHandleTypesGenerating);

src/Microsoft.Windows.CsWin32/templates/HRESULT.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ internal HRESULT ThrowOnFailure(IntPtr errorInfo = default)
6161
return this;
6262
}
6363

64-
public override string ToString() => this.Value.ToString();
64+
public override string ToString() => string.Format(global::System.Globalization.CultureInfo.InvariantCulture, "0x{0:X8}", this.Value);
6565

6666
internal string ToString(string format, IFormatProvider formatProvider) => ((uint)this.Value).ToString(format, formatProvider);
6767
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <summary>
2+
/// This class wrapper is stripped so that individual macros can be requested for generation.
3+
/// </summary>
4+
internal class PInvokeClassMacros
5+
{
6+
/// <summary>
7+
/// Creates an <see cref="global::Windows.Win32.Foundation.HRESULT"/> that represents a given <see cref="global::Windows.Win32.Foundation.WIN32_ERROR"/>.
8+
/// </summary>
9+
/// <param name="error">The win32 error to be wrapped.</param>
10+
/// <returns>An <see cref="global::Windows.Win32.Foundation.HRESULT"/>.</returns>
11+
/// <remarks>
12+
/// Learn more in <see href="https://docs.microsoft.com/windows/win32/api/winerror/nf-winerror-hresult_from_win32">the documentation for this API</see>.
13+
/// </remarks>
14+
internal static global::Windows.Win32.Foundation.HRESULT HRESULT_FROM_WIN32(global::Windows.Win32.Foundation.WIN32_ERROR error) => new(unchecked(error <= 0 ? (int)error : (int)(((uint)error & 0x0000FFFF) | 0x80070000)));
15+
}

test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,29 @@ public void HandleStructsHaveStaticNullMember(string handleName)
607607
this.AssertGeneratedMember(handleName, "Null", $"internal static {handleName} Null => default;");
608608
}
609609

610+
[Theory, PairwiseData]
611+
public void MacroAPIsGenerateWithAppropriateVisibility(bool publicVisibility)
612+
{
613+
this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { Public = publicVisibility });
614+
Assert.True(this.generator.TryGenerate("HRESULT_FROM_WIN32", CancellationToken.None));
615+
this.CollectGeneratedCode(this.generator);
616+
this.AssertNoDiagnostics();
617+
var method = Assert.Single(this.FindGeneratedMethod("HRESULT_FROM_WIN32"));
618+
619+
Assert.True(method.Modifiers.Any(publicVisibility ? SyntaxKind.PublicKeyword : SyntaxKind.InternalKeyword));
620+
}
621+
622+
[Theory]
623+
[InlineData("HRESULT_FROM_WIN32")]
624+
public void MacroAPIsGenerate(string macro)
625+
{
626+
this.generator = this.CreateGenerator();
627+
Assert.True(this.generator.TryGenerate(macro, CancellationToken.None));
628+
this.CollectGeneratedCode(this.generator);
629+
this.AssertNoDiagnostics();
630+
Assert.Single(this.FindGeneratedMethod(macro));
631+
}
632+
610633
[Theory]
611634
[InlineData("BOOL")]
612635
[InlineData("BOOLEAN")]

0 commit comments

Comments
 (0)