Skip to content

Commit

Permalink
[Feature Request] Attribute for ANSI/Unicode Suffix. Fixed #711.
Browse files Browse the repository at this point in the history
  • Loading branch information
mikebattista committed May 12, 2023
1 parent 3608e3f commit e19140c
Show file tree
Hide file tree
Showing 7 changed files with 5,568 additions and 417 deletions.
1 change: 1 addition & 0 deletions docs/projections.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ DISCLAIMER: This list is a work in progress and is not yet comprehensive.
* Calling convention is captured in the [CallingConvention](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.callingconvention) property
* Whether a function calls `SetLastError` before returning is captured in the [SetLastError](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.setlasterror) property
* Architecture-specific types are represented as types with the same name where each type is decorated with the `[SupportedArchitecture]` attribute indicating the architecture(s) where that type is supported
* Ansi and Unicode variants of APIs (-A/-W) are decorated with `[Ansi]` and `[Unicode]` attributes, respectively. Projections can choose to expose one set or the other and remove the suffix to declutter Intellisense and emulate the [unsuffixed macros provided by the headers](https://github.com/microsoft/win32metadata/blob/3608e3fff8cfecfef728bcf1811cdea9f1e86a46/generation/WinSDK/RecompiledIdlHeaders/um/synchapi.h#L446-L451). ([#711](https://github.com/microsoft/win32metadata/issues/711))
* Documentation links are captured in the `[Documentation]` attribute. Rich documentation to power Intellisense can also be loaded from the [Microsoft.Windows.SDK.Win32Docs](https://www.nuget.org/packages/Microsoft.Windows.SDK.Win32Docs/) package which provides a [MessagePack](https://msgpack.org/) dictionary where the keys are API names and the values are [ApiDetails](../apidocs/Microsoft.Windows.SDK.Win32Docs/ApiDetails.cs) objects.
* Input and output parameters are decorated with `[In]` and `[Out]` attributes. Parameters that are both input and output will contain both attributes. COM output pointer parameters are also decorated with the `[ComOutPtr]` attribute.
* Optional parameters are decorated with the `[Optional]` attribute. Optional parameters may be `NULL`.
Expand Down
14 changes: 14 additions & 0 deletions generation/WinSDK/manual/Metadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ public AlsoUsableForAttribute(string otherType)
}
}

public class AnsiAttribute : Attribute
{
public AnsiAttribute()
{
}
}

public class CanReturnErrorsAsSuccessAttribute : Attribute
{
public CanReturnErrorsAsSuccessAttribute()
Expand Down Expand Up @@ -298,4 +305,11 @@ public class SupportedOSPlatformAttribute : Attribute
public SupportedOSPlatformAttribute(string platform)
{
}
}

public class UnicodeAttribute : Attribute
{
public UnicodeAttribute()
{
}
}
5,869 changes: 5,469 additions & 400 deletions scripts/ChangesSinceLastRelease.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ public static ClangSharpSourceCompilation Create(
Dictionary<string, string> requiredNamespaces,
HashSet<string> reducePointerLevels,
IEnumerable<string> addedRefs,
Dictionary<string, string> staticLibs)
Dictionary<string, string> staticLibs,
Dictionary<string, string> apiNamesToNamespaces)
{
sourceDirectory = Path.GetFullPath(sourceDirectory);

Expand Down Expand Up @@ -220,7 +221,7 @@ public static ClangSharpSourceCompilation Create(
HashSet<string> enumsMakeFlagsHashSet = enumsMakeFlags != null ? new HashSet<string>(enumsMakeFlags) : new HashSet<string>();
System.Threading.Tasks.Parallel.ForEach(FilesToTrees(modifiedFiles), opt, (tree) =>
{
var cleanedTree = MetadataSyntaxTreeCleaner.CleanSyntaxTree(tree, remaps, enumAdditions, enumsMakeFlagsHashSet, requiredNamespaces, staticLibs, infoFinder.EmptyStructs, infoFinder.EnumMemberNames, tree.FilePath);
var cleanedTree = MetadataSyntaxTreeCleaner.CleanSyntaxTree(tree, remaps, enumAdditions, enumsMakeFlagsHashSet, requiredNamespaces, staticLibs, apiNamesToNamespaces, infoFinder.EmptyStructs, infoFinder.EnumMemberNames, tree.FilePath);
WriteTree(cleanedTree, cleanedTree.FilePath);
});

Expand Down
82 changes: 74 additions & 8 deletions sources/ClangSharpSourceToWinmd/MetadataSyntaxTreeCleaner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public static class MetadataSyntaxTreeCleaner
{
public const string ArgListVarName = "__arglist__";

public static SyntaxTree CleanSyntaxTree(SyntaxTree tree, Dictionary<string, string> remaps, Dictionary<string, Dictionary<string, string>> enumAdditions, HashSet<string> enumsMakeFlags, Dictionary<string, string> requiredNamespaces, Dictionary<string, string> staticLibs, HashSet<string> nonEmptyStructs, HashSet<string> enumMemberNames, string filePath)
public static SyntaxTree CleanSyntaxTree(SyntaxTree tree, Dictionary<string, string> remaps, Dictionary<string, Dictionary<string, string>> enumAdditions, HashSet<string> enumsMakeFlags, Dictionary<string, string> requiredNamespaces, Dictionary<string, string> staticLibs, Dictionary<string, string> apiNamesToNamespaces, HashSet<string> nonEmptyStructs, HashSet<string> enumMemberNames, string filePath)
{
TreeRewriter treeRewriter = new TreeRewriter(remaps, enumAdditions, enumsMakeFlags, requiredNamespaces, staticLibs, nonEmptyStructs, enumMemberNames);
TreeRewriter treeRewriter = new TreeRewriter(remaps, enumAdditions, enumsMakeFlags, requiredNamespaces, staticLibs, apiNamesToNamespaces, nonEmptyStructs, enumMemberNames);
var newRoot = (CSharpSyntaxNode)treeRewriter.Visit(tree.GetRoot());
var ret = CSharpSyntaxTree.Create(newRoot, null, filePath);
return ret;
Expand All @@ -35,21 +35,23 @@ private class TreeRewriter : CSharpSyntaxRewriter
private Dictionary<string, Dictionary<string, string>> enumAdditions;
private Dictionary<string, string> requiredNamespaces;
private Dictionary<string, string> staticLibs;
private Dictionary<string, string> apiNamesToNamespaces;
private HashSet<string> visitedDelegateNames = new HashSet<string>();
private HashSet<string> visitedStaticMethodNames = new HashSet<string>();
private HashSet<string> nonEmptyStructs;
private HashSet<string> enumMemberNames;
private HashSet<string> enumsToMakeFlags;
private HashSet<string> usingNamespaces = new HashSet<string>();

public TreeRewriter(Dictionary<string, string> remaps, Dictionary<string, Dictionary<string, string>> enumAdditions, HashSet<string> enumsToMakeFlags, Dictionary<string, string> requiredNamespaces, Dictionary<string, string> staticLibs, HashSet<string> nonEmptyStructs, HashSet<string> enumMemberNames)
public TreeRewriter(Dictionary<string, string> remaps, Dictionary<string, Dictionary<string, string>> enumAdditions, HashSet<string> enumsToMakeFlags, Dictionary<string, string> requiredNamespaces, Dictionary<string, string> staticLibs, Dictionary<string, string> apiNamesToNamespaces, HashSet<string> nonEmptyStructs, HashSet<string> enumMemberNames)
{
this.remaps = remaps;
this.InitRegexRemaps();

this.enumAdditions = enumAdditions;
this.requiredNamespaces = requiredNamespaces;
this.staticLibs = staticLibs;
this.apiNamesToNamespaces = apiNamesToNamespaces;
this.nonEmptyStructs = nonEmptyStructs;
this.enumMemberNames = enumMemberNames;
this.enumsToMakeFlags = enumsToMakeFlags;
Expand Down Expand Up @@ -160,7 +162,7 @@ public override SyntaxNode VisitParameter(ParameterSyntax node)

node = (ParameterSyntax)base.VisitParameter(node);
node = node.WithAttributeLists(FixRemappedAttributes(node.AttributeLists, listAttributes));

if (newName != null)
{
node = node.WithIdentifier(SyntaxFactory.Identifier(newName));
Expand Down Expand Up @@ -193,7 +195,29 @@ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
return null;
}

string name = node.Identifier.Text;
string fullName = GetFullNameWithoutArchSuffix(node);

// Add Ansi or Unicode attributes to -A/-W APIs.
if (name.EndsWith("A") && this.apiNamesToNamespaces.ContainsKey($"{name[0..^1]}W"))
{
var attributeList = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(
SyntaxFactory.ParseName("Ansi"))));

node = node.AddAttributeLists(attributeList);
}
else if (name.EndsWith("W") && this.apiNamesToNamespaces.ContainsKey($"{name[0..^1]}A"))
{
var attributeList = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(
SyntaxFactory.ParseName("Unicode"))));

node = node.AddAttributeLists(attributeList);
}

if (this.GetRemapInfo(fullName, node.AttributeLists, out var listAttributes, null, out _, out string newName))
{
node = (StructDeclarationSyntax)base.VisitStructDeclaration(node);
Expand Down Expand Up @@ -507,7 +531,9 @@ public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSynta

public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node)
{
string name = node.Identifier.Text;
string fullName = SyntaxUtils.GetFullName(node);
string fixedName = GetFullNameWithoutArchSuffix(node);

// Remove duplicate delegates in this tree
if (this.visitedDelegateNames.Contains(fullName))
Expand All @@ -517,7 +543,26 @@ public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax no

this.visitedDelegateNames.Add(fullName);

string fixedName = GetFullNameWithoutArchSuffix(node);
// Add Ansi or Unicode attributes to -A/-W APIs.
if (name.EndsWith("A") && this.apiNamesToNamespaces.ContainsKey($"{name[0..^1]}W"))
{
var attributeList = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(
SyntaxFactory.ParseName("Ansi"))));

node = node.AddAttributeLists(attributeList);
}
else if (name.EndsWith("W") && this.apiNamesToNamespaces.ContainsKey($"{name[0..^1]}A"))
{
var attributeList = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(
SyntaxFactory.ParseName("Unicode"))));

node = node.AddAttributeLists(attributeList);
}

string returnFullName = $"{fixedName}::return";

if (this.GetRemapInfo(returnFullName, node.AttributeLists, out var listAttributes, node.ReturnType.ToString(), out var newType, out _))
Expand Down Expand Up @@ -552,6 +597,7 @@ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
return null;
}

string name = node.Identifier.Text;
string fullName = SyntaxUtils.GetFullName(node);
string fixedFullName = GetFullNameWithoutArchSuffix(node);

Expand Down Expand Up @@ -584,11 +630,28 @@ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
return null;
}

string returnFullName = $"{fixedFullName}::return";
string nodeReturnType = node.ReturnType.ToString();

node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node);

// Add Ansi or Unicode attributes to -A/-W APIs.
if (name.EndsWith("A") && this.apiNamesToNamespaces.ContainsKey($"{name[0..^1]}W"))
{
var attributeList = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(
SyntaxFactory.ParseName("Ansi"))));

node = node.AddAttributeLists(attributeList);
}
else if (name.EndsWith("W") && this.apiNamesToNamespaces.ContainsKey($"{name[0..^1]}A"))
{
var attributeList = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(
SyntaxFactory.Attribute(
SyntaxFactory.ParseName("Unicode"))));

node = node.AddAttributeLists(attributeList);
}

// Add the StaticLibrary attribute if one was specified for this DLL.
if (!string.IsNullOrEmpty(dllName) && this.staticLibs.TryGetValue(dllName, out string staticLib))
{
Expand All @@ -606,6 +669,9 @@ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
node = node.WithAttributeLists(FixRemappedAttributes(node.AttributeLists, methodAttributes));
}

string returnFullName = $"{fixedFullName}::return";
string nodeReturnType = node.ReturnType.ToString();

// Find remap info for the return parameter for this method and apply any that we find
if (this.GetRemapInfo(returnFullName, node.AttributeLists, out var listAttributes, nodeReturnType, out var newType, out _))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ClangSharpSourceToWinmd
{
public static class NativeTypedefStructsCreator
{
public static void WriteToStream(Dictionary<string, string> methodNamesToNamespaces, IEnumerable<AutoType> items, Stream output)
public static void WriteToStream(Dictionary<string, string> apiNamesToNamespaces, IEnumerable<AutoType> items, Stream output)
{
using var writer = new StreamWriter(output, leaveOpen: true);
writer.Write(
Expand Down Expand Up @@ -37,7 +37,7 @@ public static void WriteToStream(Dictionary<string, string> methodNamesToNamespa

if (!string.IsNullOrEmpty(item.CloseApi))
{
if (!methodNamesToNamespaces.TryGetValue(item.CloseApi, out var apiNamespace))
if (!apiNamesToNamespaces.TryGetValue(item.CloseApi, out var apiNamespace))
{
throw new System.InvalidOperationException($"The API {item.CloseApi} was not found in the .cs files. The auto type {item.Name} needs to be given an explicit namespace.");
}
Expand Down
10 changes: 5 additions & 5 deletions sources/ClangSharpSourceToWinmd/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ public static int Run(InvocationContext context)
string archForAutoTypes = arch == "crossarch" ? "common" : arch;
string archSourceDir = Path.Combine(sourceDirectory, archForAutoTypes);

Dictionary<string, string> methodNamesToNamespaces = MetadataUtils.ScraperUtils.GetNameToNamespaceMap(sourceDirectory, MetadataUtils.ScraperUtils.NameOptions.Methods);
Dictionary<string, string> apiNamesToNamespaces = MetadataUtils.ScraperUtils.GetNameToNamespaceMap(sourceDirectory, MetadataUtils.ScraperUtils.NameOptions.Methods | MetadataUtils.ScraperUtils.NameOptions.Structs | MetadataUtils.ScraperUtils.NameOptions.Delegates);

if (requiredNamespaceValuePairs != null)
{
// Merge the requiredNamespaceForName entries into what we found the scraped source files
foreach (KeyValuePair<string, string> requiredItem in requiredNamespaces)
{
if (methodNamesToNamespaces.ContainsKey(requiredItem.Key))
if (apiNamesToNamespaces.ContainsKey(requiredItem.Key))
{
methodNamesToNamespaces[requiredItem.Key] = requiredItem.Value;
apiNamesToNamespaces[requiredItem.Key] = requiredItem.Value;
}
}
}
Expand All @@ -100,7 +100,7 @@ public static int Run(InvocationContext context)
}

using var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
NativeTypedefStructsCreator.WriteToStream(methodNamesToNamespaces, autoTypes, fileStream);
NativeTypedefStructsCreator.WriteToStream(apiNamesToNamespaces, autoTypes, fileStream);
}
}
catch (Exception e)
Expand All @@ -116,7 +116,7 @@ public static int Run(InvocationContext context)

ClangSharpSourceCompilation clangSharpCompliation =
ClangSharpSourceCompilation.Create(
sourceDirectory, arch, remaps, enumAdditions, enumMakeFlags, typeImports, requiredNamespaces, reducePointerLevels, refs, staticLibs);
sourceDirectory, arch, remaps, enumAdditions, enumMakeFlags, typeImports, requiredNamespaces, reducePointerLevels, refs, staticLibs, apiNamesToNamespaces);

System.Diagnostics.Stopwatch errorsWatch = System.Diagnostics.Stopwatch.StartNew();
Console.WriteLine(" Looking for compilation errors...");
Expand Down

0 comments on commit e19140c

Please sign in to comment.