Skip to content

Commit

Permalink
Enable creating C# projections based on multiple input winmd's and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
AArnott committed Sep 1, 2021
1 parent 99f8107 commit bdf7b39
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 162 deletions.
3 changes: 2 additions & 1 deletion src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ PInvoke001 | Functionality | Warning | SourceGenerator
PInvoke002 | Functionality | Warning | SourceGenerator
PInvoke003 | Functionality | Warning | SourceGenerator
PInvoke004 | Functionality | Warning | SourceGenerator
PInvoke005 | Functionality | Warning | SourceGenerator
PInvoke005 | Functionality | Warning | SourceGenerator
PInvoke006 | Configuration | Warning | SourceGenerator
43 changes: 40 additions & 3 deletions src/Microsoft.Windows.CsWin32/Docs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ namespace Microsoft.Windows.CsWin32
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Linq;
using MessagePack;
using Microsoft.Windows.SDK.Win32Docs;

internal class Docs
/// <summary>
/// An in-memory representation of API documentation.
/// </summary>
public class Docs
{
private static readonly Dictionary<string, Docs> DocsByPath = new Dictionary<string, Docs>(StringComparer.OrdinalIgnoreCase);

Expand All @@ -22,7 +25,12 @@ private Docs(Dictionary<string, ApiDetails> apisAndDocs)
this.apisAndDocs = apisAndDocs;
}

internal static Docs Get(string docsPath)
/// <summary>
/// Loads docs from a file.
/// </summary>
/// <param name="docsPath">The messagepack docs file to read from.</param>
/// <returns>An instance of <see cref="Docs"/> that accesses the documentation in the file specified by <paramref name="docsPath"/>.</returns>
public static Docs Get(string docsPath)
{
lock (DocsByPath)
{
Expand All @@ -48,6 +56,35 @@ internal static Docs Get(string docsPath)
}
}

/// <summary>
/// Returns a <see cref="Docs"/> instance that contains all the merged documentation from a list of docs.
/// </summary>
/// <param name="docs">The docs to be merged. When API documentation is provided by multiple docs in this list, the first one appearing in this list is taken.</param>
/// <returns>An instance that contains all the docs provided. When <paramref name="docs"/> contains exactly one element, that element is returned.</returns>
public static Docs Merge(IReadOnlyList<Docs> docs)
{
if (docs.Count == 1)
{
// Nothing to merge.
return docs[0];
}

Dictionary<string, ApiDetails> mergedDocs = new(docs.Sum(d => d.apisAndDocs.Count), StringComparer.OrdinalIgnoreCase);
foreach (Docs doc in docs)
{
foreach (KeyValuePair<string, ApiDetails> api in doc.apisAndDocs)
{
// We want a first one wins policy.
if (!mergedDocs.ContainsKey(api.Key))
{
mergedDocs.Add(api.Key, api.Value);
}
}
}

return new Docs(mergedDocs);
}

internal bool TryGetApiDocs(string apiName, [NotNullWhen(true)] out ApiDetails? docs) => this.apisAndDocs.TryGetValue(apiName, out docs);
}
}
32 changes: 16 additions & 16 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,16 +286,16 @@ public class Generator : IDisposable
/// Initializes a new instance of the <see cref="Generator"/> class.
/// </summary>
/// <param name="metadataLibraryPath">The path to the winmd metadata to generate APIs from.</param>
/// <param name="apiDocsPath">The path to the API docs file.</param>
/// <param name="docs">The API docs to include in the generated code.</param>
/// <param name="options">Options that influence the result of generation.</param>
/// <param name="compilation">The compilation that the generated code will be added to.</param>
/// <param name="parseOptions">The parse options that will be used for the generated code.</param>
public Generator(string metadataLibraryPath, string? apiDocsPath, GeneratorOptions? options = null, CSharpCompilation? compilation = null, CSharpParseOptions? parseOptions = null)
public Generator(string metadataLibraryPath, Docs? docs, GeneratorOptions options, CSharpCompilation? compilation = null, CSharpParseOptions? parseOptions = null)
{
this.MetadataIndex = MetadataIndex.Get(metadataLibraryPath, compilation?.Options.Platform);
this.ApiDocs = apiDocsPath is object ? Docs.Get(apiDocsPath) : null;
this.ApiDocs = docs;

this.options = options ??= new GeneratorOptions();
this.options = options;
this.options.Validate();
this.compilation = compilation;
this.parseOptions = parseOptions;
Expand All @@ -312,11 +312,6 @@ public Generator(string metadataLibraryPath, string? apiDocsPath, GeneratorOptio
this.generateSupportedOSPlatformAttributesOnInterfaces = (targets & AttributeTargets.Interface) == AttributeTargets.Interface;
}

if (options.AllowMarshaling)
{
this.BannedAPIs.Add("VARIANT", "Use `object` instead of VARIANT when in COM interface mode. VARIANT can only be emitted when emitting COM interfaces as structs.");
}

bool useComInterfaces = options.AllowMarshaling;
this.generalTypeSettings = new TypeSyntaxSettings(
this,
Expand All @@ -343,13 +338,16 @@ private enum FriendlyOverloadOf
InterfaceMethod,
}

internal Dictionary<string, string> BannedAPIs { get; } = new Dictionary<string, string>
{
{ "GetLastError", "Do not generate GetLastError. Call Marshal.GetLastWin32Error() instead. Learn more from https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getlastwin32error" },
{ "OLD_LARGE_INTEGER", "Use the C# long keyword instead." },
{ "LARGE_INTEGER", "Use the C# long keyword instead." },
{ "ULARGE_INTEGER", "Use the C# ulong keyword instead." },
};
internal static ImmutableDictionary<string, string> BannedAPIsWithoutMarshaling { get; } = ImmutableDictionary<string, string>.Empty
.Add("GetLastError", "Do not generate GetLastError. Call Marshal.GetLastWin32Error() instead. Learn more from https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.marshal.getlastwin32error")
.Add("OLD_LARGE_INTEGER", "Use the C# long keyword instead.")
.Add("LARGE_INTEGER", "Use the C# long keyword instead.")
.Add("ULARGE_INTEGER", "Use the C# ulong keyword instead.");

internal static ImmutableDictionary<string, string> BannedAPIsWithMarshaling { get; } = BannedAPIsWithoutMarshaling
.Add("VARIANT", "Use `object` instead of VARIANT when in COM interface mode. VARIANT can only be emitted when emitting COM interfaces as structs.");

internal ImmutableDictionary<string, string> BannedAPIs => GetBannedAPIs(this.options);

internal MetadataIndex MetadataIndex { get; }

Expand Down Expand Up @@ -970,6 +968,8 @@ nsContents.Key is object
return normalizedResults;
}

internal static ImmutableDictionary<string, string> GetBannedAPIs(GeneratorOptions options) => options.AllowMarshaling ? BannedAPIsWithMarshaling : BannedAPIsWithoutMarshaling;

[return: NotNullIfNotNull("marshalAs")]
internal static AttributeSyntax? MarshalAs(MarshalAsAttribute? marshalAs)
{
Expand Down
Loading

0 comments on commit bdf7b39

Please sign in to comment.