Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to HRESULT #666

Merged
merged 6 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Create a `NativeMethods.txt` file in your project directory that lists the APIs
Each line may consist of *one* of the following:

* 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.
* 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.
* 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).
* Module name followed by `.*` to generate all methods exported from that module (e.g. `Kernel32.*`).
* 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.
Expand Down
163 changes: 140 additions & 23 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public class Generator : IDisposable
private const string OriginalDelegateAnnotation = "OriginalDelegate";

private static readonly Dictionary<string, MethodDeclarationSyntax> PInvokeHelperMethods;
private static readonly Dictionary<string, MethodDeclarationSyntax> PInvokeMacros;

private static readonly string AutoGeneratedHeader = @"// ------------------------------------------------------------------------------
// <auto-generated>
Expand All @@ -98,6 +99,12 @@ public class Generator : IDisposable
/// <content>
/// Contains extern methods from ""{0}"".
/// </content>
".Replace("\r\n", "\n");

private static readonly string PartialPInvokeMacrosContentComment = @"
/// <content>
/// Contains macros.
/// </content>
".Replace("\r\n", "\n");

private static readonly SyntaxTriviaList InlineArrayUnsafeAsSpanComment = ParseLeadingTrivia(@"/// <summary>
Expand Down Expand Up @@ -348,6 +355,7 @@ public class Generator : IDisposable
private readonly IdentifierNameSyntax methodsAndConstantsClassName;
private readonly HashSet<string> injectedPInvokeHelperMethods = new();
private readonly HashSet<string> injectedPInvokeHelperMethodsToFriendlyOverloadsExtensions = new();
private readonly HashSet<string> injectedPInvokeMacros = new();
private readonly Dictionary<TypeDefinitionHandle, bool> managedTypesCheck = new();
private bool needsWinRTCustomMarshaler;

Expand All @@ -359,6 +367,13 @@ static Generator()
}

PInvokeHelperMethods = ((ClassDeclarationSyntax)member).Members.OfType<MethodDeclarationSyntax>().ToDictionary(m => m.Identifier.ValueText, m => m);

if (!TryFetchTemplate("PInvokeClassMacros", null, out member))
{
throw new GenerationFailedException("Missing embedded resource.");
}

PInvokeMacros = ((ClassDeclarationSyntax)member).Members.OfType<MethodDeclarationSyntax>().ToDictionary(m => m.Identifier.ValueText, m => m);
}

/// <summary>
Expand Down Expand Up @@ -477,34 +492,35 @@ private IEnumerable<MemberDeclarationSyntax> NamespaceMembers
{
IEnumerable<IGrouping<string, MemberDeclarationSyntax>> members = this.committedCode.MembersByModule;
IEnumerable<MemberDeclarationSyntax> result = Enumerable.Empty<MemberDeclarationSyntax>();
for (int i = 0; i < members.Count(); i++)
int i = 0;
foreach (IGrouping<string, MemberDeclarationSyntax> entry in members)
{
IGrouping<string, MemberDeclarationSyntax> entry = members.ElementAt(i);
if (i == 0)
ClassDeclarationSyntax partialClass = DeclarePInvokeClass(entry.Key)
.AddMembers(entry.ToArray())
.WithLeadingTrivia(ParseLeadingTrivia(string.Format(CultureInfo.InvariantCulture, PartialPInvokeContentComment, entry.Key)));
if (i++ == 0)
AArnott marked this conversation as resolved.
Show resolved Hide resolved
{
result = result.Concat(new MemberDeclarationSyntax[]
{
ClassDeclaration(Identifier(this.options.ClassName))
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.PartialKeyword))
.AddMembers(entry.ToArray())
partialClass = partialClass
.WithoutLeadingTrivia()
.AddAttributeLists(AttributeList().AddAttributes(GeneratedCodeAttribute))
.WithLeadingTrivia(ParseLeadingTrivia(string.Format(CultureInfo.InvariantCulture, PartialPInvokeContentComment, entry.Key)))
.WithAdditionalAnnotations(new SyntaxAnnotation(SimpleFileNameAnnotation, $"{this.options.ClassName}.{entry.Key}")),
});
}
else
{
result = result.Concat(new MemberDeclarationSyntax[]
{
ClassDeclaration(Identifier(this.options.ClassName))
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.PartialKeyword))
.AddMembers(entry.ToArray())
.WithLeadingTrivia(ParseLeadingTrivia(string.Format(CultureInfo.InvariantCulture, PartialPInvokeContentComment, entry.Key)))
.WithAdditionalAnnotations(new SyntaxAnnotation(SimpleFileNameAnnotation, $"{this.options.ClassName}.{entry.Key}")),
});
.WithLeadingTrivia(partialClass.GetLeadingTrivia());
}

result = result.Concat(new MemberDeclarationSyntax[] { partialClass });
}

ClassDeclarationSyntax macrosPartialClass = DeclarePInvokeClass("Macros")
.AddMembers(this.committedCode.Macros.ToArray())
.WithLeadingTrivia(ParseLeadingTrivia(PartialPInvokeMacrosContentComment));
if (macrosPartialClass.Members.Count > 0)
{
result = result.Concat(new MemberDeclarationSyntax[] { macrosPartialClass });
}

ClassDeclarationSyntax DeclarePInvokeClass(string fileNameKey) => ClassDeclaration(Identifier(this.options.ClassName))
.AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.StaticKeyword), TokenWithSpace(SyntaxKind.PartialKeyword))
.WithAdditionalAnnotations(new SyntaxAnnotation(SimpleFileNameAnnotation, $"{this.options.ClassName}.{fileNameKey}"));

result = result.Concat(this.committedCode.GeneratedTypes);

ClassDeclarationSyntax inlineArrayIndexerExtensionsClass = this.DeclareInlineArrayIndexerExtensionsClass();
Expand Down Expand Up @@ -574,6 +590,8 @@ public void GenerateAll(CancellationToken cancellationToken)
this.RequestAllInteropTypes(cancellationToken);

this.GenerateAllConstants(cancellationToken);

this.GenerateAllMacros(cancellationToken);
}

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

result = this.TryGenerateMacro(apiNameOrModuleWildcard, out preciseApi);
if (result || preciseApi.Count > 1)
{
return result;
}

return false;
}
}
Expand Down Expand Up @@ -767,6 +791,30 @@ public void GenerateAllConstants(CancellationToken cancellationToken)
}
}

/// <summary>
/// Generates a projection of all macros.
/// </summary>
/// <param name="cancellationToken">A cancellation token.</param>
public void GenerateAllMacros(CancellationToken cancellationToken)
{
foreach (KeyValuePair<string, MethodDeclarationSyntax> macro in PInvokeMacros)
{
cancellationToken.ThrowIfCancellationRequested();

try
{
this.volatileCode.GenerationTransaction(delegate
{
this.RequestMacro(macro.Value);
});
}
catch (GenerationFailedException ex) when (IsPlatformCompatibleException(ex))
{
// Something transitively required for this field is not available for this platform, so skip this method.
}
}
}

/// <summary>
/// Generates all extern methods exported from a particular module, along with all their supporting types.
/// </summary>
Expand Down Expand Up @@ -1038,6 +1086,34 @@ public bool TryGenerateConstant(string possiblyQualifiedName, out IReadOnlyList<
return false;
}

/// <summary>
/// Generate code for the named macro, if it is recognized.
/// </summary>
/// <param name="macroName">The name of the macro. Never qualified with a namespace.</param>
/// <param name="preciseApi">Receives the canonical API names that <paramref name="macroName"/> matched on.</param>
/// <returns><see langword="true"/> if a match was found and the macro generated; otherwise <see langword="false"/>.</returns>
public bool TryGenerateMacro(string macroName, out IReadOnlyList<string> preciseApi)
{
if (macroName is null)
{
throw new ArgumentNullException(nameof(macroName));
}
AArnott marked this conversation as resolved.
Show resolved Hide resolved

if (!PInvokeMacros.TryGetValue(macroName, out MethodDeclarationSyntax macro))
{
preciseApi = Array.Empty<string>();
return false;
}

this.volatileCode.GenerationTransaction(delegate
{
this.RequestMacro(macro);
});

preciseApi = ImmutableList.Create(macroName);
return true;
}

/// <summary>
/// Produces a sequence of suggested APIs with a similar name to the specified one.
/// </summary>
Expand Down Expand Up @@ -1525,6 +1601,24 @@ internal void RequestConstant(FieldDefinitionHandle fieldDefHandle)
});
}

internal void RequestMacro(MethodDeclarationSyntax macro)
{
this.volatileCode.GenerateMacro(macro.Identifier.ValueText, delegate
{
this.volatileCode.AddMacro(macro.Identifier.ValueText, (MethodDeclarationSyntax)this.ElevateVisibility(macro));

// Generate any additional types that this macro relies on.
foreach (QualifiedNameSyntax identifier in macro.DescendantNodes().OfType<QualifiedNameSyntax>())
{
string identifierString = identifier.ToString();
if (identifierString.StartsWith(GlobalNamespacePrefix, StringComparison.Ordinal))
{
this.TryGenerateType(identifierString.Substring(GlobalNamespacePrefix.Length));
}
}
});
}

internal TypeSyntax? RequestSafeHandle(string releaseMethod)
{
if (!this.options.UseSafeHandles)
Expand Down Expand Up @@ -5901,6 +5995,8 @@ private class GeneratedCode

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

private readonly Dictionary<string, MethodDeclarationSyntax> macros = new(StringComparer.Ordinal);

/// <summary>
/// The set of types that are or have been generated so we don't stack overflow for self-referencing types.
/// </summary>
Expand Down Expand Up @@ -5934,7 +6030,7 @@ internal GeneratedCode(GeneratedCode parent)
}

internal bool IsEmpty => this.modulesAndMembers.Count == 0 && this.types.Count == 0 && this.fieldsToSyntax.Count == 0 && this.safeHandleTypes.Count == 0 && this.specialTypes.Count == 0
&& this.inlineArrayIndexerExtensionsMembers.Count == 0 && this.comInterfaceFriendlyExtensionsMembers.Count == 0;
&& this.inlineArrayIndexerExtensionsMembers.Count == 0 && this.comInterfaceFriendlyExtensionsMembers.Count == 0 && this.macros.Count == 0;

internal IEnumerable<MemberDeclarationSyntax> GeneratedTypes => this.GetTypesWithInjectedFields()
.Concat(this.specialTypes.Values.Where(st => !st.TopLevel).Select(st => st.Type))
Expand All @@ -5961,6 +6057,8 @@ internal GeneratedCode(GeneratedCode parent)
}
}

internal IEnumerable<MethodDeclarationSyntax> Macros => this.macros.Values;

internal void AddSafeHandleType(ClassDeclarationSyntax safeHandleDeclaration)
{
this.ThrowIfNotGenerating();
Expand Down Expand Up @@ -5998,6 +6096,12 @@ internal void AddConstant(FieldDefinitionHandle fieldDefHandle, FieldDeclaration
this.fieldsToSyntax.Add(fieldDefHandle, (constantDeclaration, fieldType));
}

internal void AddMacro(string macroName, MethodDeclarationSyntax macro)
{
this.ThrowIfNotGenerating();
this.macros.Add(macroName, macro);
}

internal void AddInlineArrayIndexerExtension(MethodDeclarationSyntax inlineIndexer)
{
this.ThrowIfNotGenerating();
Expand Down Expand Up @@ -6161,6 +6265,18 @@ internal void GenerateConstant(FieldDefinitionHandle fieldDefinitionHandle, Acti
generator();
}

internal void GenerateMacro(string macroName, Action generator)
{
this.ThrowIfNotGenerating();

if (this.macros.ContainsKey(macroName) || this.parent?.macros.ContainsKey(macroName) is true)
{
return;
}

generator();
}

internal bool TryGetSafeHandleForReleaseMethod(string releaseMethod, out TypeSyntax? safeHandleType)
{
return this.releaseMethodsWithSafeHandleTypesGenerating.TryGetValue(releaseMethod, out safeHandleType)
Expand Down Expand Up @@ -6219,6 +6335,7 @@ private void Commit(GeneratedCode? parent)
Commit(this.safeHandleTypes, parent?.safeHandleTypes);
Commit(this.specialTypes, parent?.specialTypes);
Commit(this.typesGenerating, parent?.typesGenerating);
Commit(this.macros, parent?.macros);
Commit(this.methodsGenerating, parent?.methodsGenerating);
Commit(this.specialTypesGenerating, parent?.specialTypesGenerating);
Commit(this.releaseMethodsWithSafeHandleTypesGenerating, parent?.releaseMethodsWithSafeHandleTypesGenerating);
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.Windows.CsWin32/templates/HRESULT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ internal HRESULT ThrowOnFailure(IntPtr errorInfo = default)
return this;
}

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

internal string ToString(string format, IFormatProvider formatProvider) => ((uint)this.Value).ToString(format, formatProvider);
}
15 changes: 15 additions & 0 deletions src/Microsoft.Windows.CsWin32/templates/PInvokeClassMacros.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <summary>
/// This class wrapper is stripped so that individual macros can be requested for generation.
/// </summary>
internal class PInvokeClassMacros
{
/// <summary>
AArnott marked this conversation as resolved.
Show resolved Hide resolved
/// Creates an <see cref="global::Windows.Win32.Foundation.HRESULT"/> that represents a given <see cref="global::Windows.Win32.Foundation.WIN32_ERROR"/>.
/// </summary>
/// <param name="error">The win32 error to be wrapped.</param>
/// <returns>An <see cref="global::Windows.Win32.Foundation.HRESULT"/>.</returns>
/// <remarks>
/// Learn more in <see href="https://docs.microsoft.com/windows/win32/api/winerror/nf-winerror-hresult_from_win32">the documentation for this API</see>.
/// </remarks>
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)));
}
23 changes: 23 additions & 0 deletions test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,29 @@ public void HandleStructsHaveStaticNullMember(string handleName)
this.AssertGeneratedMember(handleName, "Null", $"internal static {handleName} Null => default;");
}

[Theory, PairwiseData]
public void MacroAPIsGenerateWithAppropriateVisibility(bool publicVisibility)
{
this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { Public = publicVisibility });
Assert.True(this.generator.TryGenerate("HRESULT_FROM_WIN32", CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
var method = Assert.Single(this.FindGeneratedMethod("HRESULT_FROM_WIN32"));

Assert.True(method.Modifiers.Any(publicVisibility ? SyntaxKind.PublicKeyword : SyntaxKind.InternalKeyword));
}

[Theory]
[InlineData("HRESULT_FROM_WIN32")]
public void MacroAPIsGenerate(string macro)
{
this.generator = this.CreateGenerator();
Assert.True(this.generator.TryGenerate(macro, CancellationToken.None));
this.CollectGeneratedCode(this.generator);
this.AssertNoDiagnostics();
Assert.Single(this.FindGeneratedMethod(macro));
}

[Theory]
[InlineData("BOOL")]
[InlineData("BOOLEAN")]
Expand Down