From d17ddb7dc61209e6d44ae0ce04143c2663e928ff Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 15 Mar 2021 20:30:24 -0600 Subject: [PATCH 1/7] Update metadata to 10.0.19041.5-preview.68 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index d71746e2..fae99695 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,7 +23,7 @@ true snupkg - 10.0.19041.5-preview.27 + 10.0.19041.5-preview.68 From 532f9fc51155863ad9b79ec8ca117960218063df Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 16 Mar 2021 08:39:31 -0600 Subject: [PATCH 2/7] Support new typed constants and interfaces with no GuidAttribute --- src/Microsoft.Windows.CsWin32/Generator.cs | 148 +++++++++++++----- .../GeneratorTests.cs | 3 + 2 files changed, 110 insertions(+), 41 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 41be6c2b..2c21df8b 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -414,7 +414,7 @@ public Generator(Stream metadataLibraryStream, GeneratorOptions? options = null, foreach (FieldDefinitionHandle fieldDefHandle in this.Apis.SelectMany(api => api.GetFields())) { FieldDefinition fieldDef = this.mr.GetFieldDefinition(fieldDefHandle); - const FieldAttributes expectedFlags = FieldAttributes.Literal | FieldAttributes.Static | FieldAttributes.Public; + const FieldAttributes expectedFlags = FieldAttributes.Static | FieldAttributes.Public; if ((fieldDef.Attributes & expectedFlags) == expectedFlags) { string name = this.mr.GetString(fieldDef.Name); @@ -1262,6 +1262,20 @@ internal void GetBaseTypeInfo(TypeDefinition typeDef, out StringHandle baseTypeN return null; } + internal CustomAttribute? FindInteropDecorativeAttribute(CustomAttributeHandleCollection customAttributeHandles, string attributeName) + { + foreach (var handle in customAttributeHandles) + { + CustomAttribute att = this.mr.GetCustomAttribute(handle); + if (this.IsAttribute(att, InteropDecorationNamespace, attributeName)) + { + return att; + } + } + + return null; + } + /// /// Attempts to translate a to a . /// @@ -1805,6 +1819,65 @@ private static bool IsAnsiFunction(string methodName) return false; } + private static unsafe string ToHex(T value) + where T : unmanaged + { + int fullHexLength = sizeof(T) * 2; + string hex = string.Format(CultureInfo.InvariantCulture, "0x{0:X" + fullHexLength + "}", value); + return hex; + } + + private static ObjectCreationExpressionSyntax GuidValue(CustomAttribute guidAttribute) + { + CustomAttributeValue args = guidAttribute.DecodeValue(CustomAttributeTypeProvider.Instance); + var a = (uint)args.FixedArguments[0].Value!; + var b = (ushort)args.FixedArguments[1].Value!; + var c = (ushort)args.FixedArguments[2].Value!; + var d = (byte)args.FixedArguments[3].Value!; + var e = (byte)args.FixedArguments[4].Value!; + var f = (byte)args.FixedArguments[5].Value!; + var g = (byte)args.FixedArguments[6].Value!; + var h = (byte)args.FixedArguments[7].Value!; + var i = (byte)args.FixedArguments[8].Value!; + var j = (byte)args.FixedArguments[9].Value!; + var k = (byte)args.FixedArguments[10].Value!; + + return ObjectCreationExpression(IdentifierName(nameof(Guid))).AddArgumentListArguments( + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(a), a))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(b), b))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(c), c))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(d), d))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(e), e))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(f), f))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(g), g))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(h), h))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(i), i))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(j), j))), + Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(k), k)))); + } + + private static ObjectCreationExpressionSyntax PropertyKeyValue(CustomAttribute propertyKeyAttribute) + { + CustomAttributeValue args = propertyKeyAttribute.DecodeValue(CustomAttributeTypeProvider.Instance); + var a = (uint)args.FixedArguments[0].Value!; + var b = (ushort)args.FixedArguments[1].Value!; + var c = (ushort)args.FixedArguments[2].Value!; + var d = (byte)args.FixedArguments[3].Value!; + var e = (byte)args.FixedArguments[4].Value!; + var f = (byte)args.FixedArguments[5].Value!; + var g = (byte)args.FixedArguments[6].Value!; + var h = (byte)args.FixedArguments[7].Value!; + var i = (byte)args.FixedArguments[8].Value!; + var j = (byte)args.FixedArguments[9].Value!; + var k = (byte)args.FixedArguments[10].Value!; + var pid = (uint)args.FixedArguments[11].Value!; + + return ObjectCreationExpression(IdentifierName("PROPERTYKEY")).WithInitializer( + InitializerExpression(SyntaxKind.ObjectInitializerExpression).AddExpressions( + AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("fmtid"), GuidValue(propertyKeyAttribute)), + AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("pid"), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(pid))))); + } + private MemberDeclarationSyntax FetchTemplate(string name) { using Stream? templateStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{ThisAssembly.RootNamespace}.templates.{name}"); @@ -2101,9 +2174,12 @@ private FieldDeclarationSyntax DeclareField(FieldDefinitionHandle fieldDefHandle TypeHandleInfo fieldTypeInfo = fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null); var customAttributes = fieldDef.GetCustomAttributes(); var fieldType = fieldTypeInfo.ToTypeSyntax(this.fieldTypeSettings, customAttributes); - Constant constant = this.mr.GetConstant(fieldDef.GetDefaultValue()); - ExpressionSyntax value = this.ToExpressionSyntax(constant); - if (fieldType.Type is not PredefinedTypeSyntax) + ExpressionSyntax value = + fieldDef.GetDefaultValue() is { IsNil: false } constantHandle ? this.ToExpressionSyntax(this.mr.GetConstant(constantHandle)) : + this.FindInteropDecorativeAttribute(customAttributes, nameof(GuidAttribute)) is CustomAttribute guidAttribute ? GuidValue(guidAttribute) : + this.FindInteropDecorativeAttribute(customAttributes, "PropertyKeyAttribute") is CustomAttribute propertyKeyAttribute ? PropertyKeyValue(propertyKeyAttribute) : + throw new NotSupportedException("Unsupported constant: " + name); + if (fieldType.Type is not PredefinedTypeSyntax && value is not ObjectCreationExpressionSyntax) { if (fieldType.Type is IdentifierNameSyntax { Identifier: { ValueText: string typeName } } && this.TryGetHandleReleaseMethod(typeName, out _)) { @@ -2117,7 +2193,7 @@ private FieldDeclarationSyntax DeclareField(FieldDefinitionHandle fieldDefHandle } var modifiers = TokenList(Token(this.Visibility)); - if (this.IsTypeDefStruct(fieldType.Type as IdentifierNameSyntax)) + if (this.IsTypeDefStruct(fieldType.Type as IdentifierNameSyntax) || value is ObjectCreationExpressionSyntax) { modifiers = modifiers.Add(Token(SyntaxKind.StaticKeyword)).Add(Token(SyntaxKind.ReadOnlyKeyword)); } @@ -2300,11 +2376,9 @@ private TypeDeclarationSyntax DeclareInterfaceAsStruct(TypeDefinition typeDef, I .AddModifiers(Token(this.Visibility), Token(SyntaxKind.UnsafeKeyword)) .AddMembers(members.ToArray()); - Guid guid = this.FindGuidFromAttribute(typeDef); - if (guid != Guid.Empty) + if (this.FindGuidFromAttribute(typeDef) is Guid guid) { - iface = iface - .AddAttributeLists(AttributeList().AddAttributes(GUID(guid))); + iface = iface.AddAttributeLists(AttributeList().AddAttributes(GUID(guid))); } return iface; @@ -2443,12 +2517,15 @@ private TypeDeclarationSyntax DeclareInterfaceAsStruct(TypeDefinition typeDef, I } } - Guid guid = this.FindGuidFromAttribute(typeDef); InterfaceDeclarationSyntax ifaceDeclaration = InterfaceDeclaration(ifaceName.Identifier) - .AddAttributeLists(AttributeList().AddAttributes(GUID(guid), ifaceType)) .AddModifiers(Token(this.Visibility)) .AddMembers(members.ToArray()); + if (this.FindGuidFromAttribute(typeDef) is Guid guid) + { + ifaceDeclaration = ifaceDeclaration.AddAttributeLists(AttributeList().AddAttributes(GUID(guid), ifaceType)); + } + if (baseTypeSyntaxList.Count > 0) { ifaceDeclaration = ifaceDeclaration @@ -2458,28 +2535,26 @@ private TypeDeclarationSyntax DeclareInterfaceAsStruct(TypeDefinition typeDef, I return ifaceDeclaration; } - private Guid FindGuidFromAttribute(TypeDefinition typeDef) + private Guid? FindGuidFromAttribute(TypeDefinition typeDef) => this.FindGuidFromAttribute(typeDef.GetCustomAttributes()); + + private Guid? FindGuidFromAttribute(CustomAttributeHandleCollection attributes) { - Guid guid = Guid.Empty; - foreach (CustomAttributeHandle attHandle in typeDef.GetCustomAttributes()) + Guid? guid = null; + if (this.FindInteropDecorativeAttribute(attributes, nameof(GuidAttribute)) is CustomAttribute att) { - CustomAttribute att = this.mr.GetCustomAttribute(attHandle); - if (this.IsAttribute(att, InteropDecorationNamespace, nameof(GuidAttribute))) - { - CustomAttributeValue args = att.DecodeValue(CustomAttributeTypeProvider.Instance); - guid = new Guid( - (uint)args.FixedArguments[0].Value!, - (ushort)args.FixedArguments[1].Value!, - (ushort)args.FixedArguments[2].Value!, - (byte)args.FixedArguments[3].Value!, - (byte)args.FixedArguments[4].Value!, - (byte)args.FixedArguments[5].Value!, - (byte)args.FixedArguments[6].Value!, - (byte)args.FixedArguments[7].Value!, - (byte)args.FixedArguments[8].Value!, - (byte)args.FixedArguments[9].Value!, - (byte)args.FixedArguments[10].Value!); - } + CustomAttributeValue args = att.DecodeValue(CustomAttributeTypeProvider.Instance); + guid = new Guid( + (uint)args.FixedArguments[0].Value!, + (ushort)args.FixedArguments[1].Value!, + (ushort)args.FixedArguments[2].Value!, + (byte)args.FixedArguments[3].Value!, + (byte)args.FixedArguments[4].Value!, + (byte)args.FixedArguments[5].Value!, + (byte)args.FixedArguments[6].Value!, + (byte)args.FixedArguments[7].Value!, + (byte)args.FixedArguments[8].Value!, + (byte)args.FixedArguments[9].Value!, + (byte)args.FixedArguments[10].Value!); } return guid; @@ -2617,8 +2692,7 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinition typeDef) result = result.AddAttributeLists(AttributeList().AddAttributes(StructLayout(typeDef.Attributes, layout))); } - Guid guid = this.FindGuidFromAttribute(typeDef); - if (guid != Guid.Empty) + if (this.FindGuidFromAttribute(typeDef) is Guid guid) { result = result.AddAttributeLists(AttributeList().AddAttributes(GUID(guid))); } @@ -3991,14 +4065,6 @@ private ExpressionSyntax ToHexExpressionSyntax(Constant constant) ConstantTypeCode.UInt64 => LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(ToHex(blobReader.ReadUInt64()), blobReader2.ReadUInt64())), _ => throw new NotSupportedException("ConstantTypeCode not supported: " + constant.TypeCode), }; - - unsafe string ToHex(T value) - where T : unmanaged - { - int fullHexLength = sizeof(T) * 2; - string hex = string.Format(CultureInfo.InvariantCulture, "0x{0:X" + fullHexLength + "}", value); - return hex; - } } } } diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index f9c19de2..acf32c03 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -131,6 +131,9 @@ public void InterestingAPIs( "RTM_DEST_INFO", // nested structs with inline arrays with element whose name collides with another "DISPPARAMS", "JsVariantToValue", + "WIA_CATEGORY_FINISHED_FILE", // GUID constant + "DEVPKEY_MTPBTH_IsConnected", // PROPERTYKEY constant + "IOleUILinkContainerW", // An IUnknown-derived interface with no GUID "FILE_TYPE_NOTIFICATION_INPUT", "DS_SELECTION_LIST", // A struct with a fixed-length inline array of potentially managed structs "ISpellCheckerFactory", // COM interface that includes `ref` parameters From fd347dd033ff6955105d5399983d8ba6682dd5d4 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 16 Mar 2021 09:13:56 -0600 Subject: [PATCH 3/7] Compensate for BSTR using `char*` field It used to use `IntPtr`. --- src/Microsoft.Windows.CsWin32/Generator.cs | 42 ++++++++++++++-------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 2c21df8b..4162def5 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -2788,13 +2788,26 @@ private StructDeclarationSyntax DeclareTypeDefStruct(TypeDefinition typeDef) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); - // public static explicit operator MSIHANDLE(IntPtr value) => new MSIHANDLE((uint)value.ToInt32()); - members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ExplicitKeyword), name) - .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(IntPtrTypeSyntax)) - .WithExpressionBody(ArrowExpressionClause(ObjectCreationExpression(name).AddArgumentListArguments( - Argument(CastExpression(fieldInfo.FieldType, InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueParameter, IdentifierName(nameof(IntPtr.ToInt32))))))))) - .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public - .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); + if (fieldInfo.FieldType is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.UIntKeyword } }) + { + // public static explicit operator MSIHANDLE(IntPtr value) => new MSIHANDLE((uint)value.ToInt32()); + members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ExplicitKeyword), name) + .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(IntPtrTypeSyntax)) + .WithExpressionBody(ArrowExpressionClause(ObjectCreationExpression(name).AddArgumentListArguments( + Argument(CastExpression(fieldInfo.FieldType, InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueParameter, IdentifierName(nameof(IntPtr.ToInt32))))))))) + .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); + } + else if (fieldInfo.FieldType is PointerTypeSyntax) + { + // public static explicit operator MSIHANDLE(IntPtr value) => new MSIHANDLE(value.ToPointer()); + members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ExplicitKeyword), name) + .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(IntPtrTypeSyntax)) + .WithExpressionBody(ArrowExpressionClause(ObjectCreationExpression(name).AddArgumentListArguments( + Argument(CastExpression(fieldInfo.FieldType, InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueParameter, IdentifierName(nameof(IntPtr.ToPointer))))))))) + .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); + } } // public bool Equals(HWND other) => this.Value == other.Value; @@ -2901,7 +2914,7 @@ private IEnumerable CreateAdditionalTypeDefBSTRMembers( { ExpressionSyntax thisValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("Value")); - // public override string ToString() => Marshal.PtrToStringBSTR(this.Value); + // public override string ToString() => Marshal.PtrToStringBSTR(new IntPtr(this.Value)); yield return MethodDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)), nameof(this.ToString)) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)) .WithExpressionBody(ArrowExpressionClause( @@ -2910,14 +2923,13 @@ private IEnumerable CreateAdditionalTypeDefBSTRMembers( SyntaxKind.SimpleMemberAccessExpression, IdentifierName(nameof(Marshal)), IdentifierName(nameof(Marshal.PtrToStringBSTR)))) - .AddArgumentListArguments(Argument(thisValue)))) + .AddArgumentListArguments(Argument(ObjectCreationExpression(IntPtrTypeSyntax).AddArgumentListArguments(Argument(thisValue)))))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); - // public static implicit operator ReadOnlySpan(BSTR bstr) => bstr.Value != IntPtr.Zero ? new ReadOnlySpan(bstr.Value.ToPointer(), *((int*)bstr.Value.ToPointer() - 1) / 2) : default; + // public static implicit operator ReadOnlySpan(BSTR bstr) => bstr.Value != null ? new ReadOnlySpan(bstr.Value, *((int*)bstr.Value - 1) / 2) : default; TypeSyntax rosChar = MakeReadOnlySpanOfT(PredefinedType(Token(SyntaxKind.CharKeyword))); IdentifierNameSyntax bstrParam = IdentifierName("bstr"); ExpressionSyntax bstrValue = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, bstrParam, IdentifierName("Value")); - ExpressionSyntax bstrPointer = InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, bstrValue, IdentifierName(nameof(IntPtr.ToPointer))), ArgumentList()); ExpressionSyntax length = BinaryExpression( SyntaxKind.DivideExpression, PrefixUnaryExpression( @@ -2925,12 +2937,12 @@ private IEnumerable CreateAdditionalTypeDefBSTRMembers( ParenthesizedExpression( BinaryExpression( SyntaxKind.SubtractExpression, - CastExpression(PointerType(PredefinedType(Token(SyntaxKind.IntKeyword))), bstrPointer), + CastExpression(PointerType(PredefinedType(Token(SyntaxKind.IntKeyword))), bstrValue), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(1))))), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(2))); - ExpressionSyntax rosCreation = ObjectCreationExpression(rosChar).AddArgumentListArguments(Argument(bstrPointer), Argument(length)); - ExpressionSyntax bstrNotZero = BinaryExpression(SyntaxKind.NotEqualsExpression, bstrValue, DefaultExpression(IntPtrTypeSyntax)); - ExpressionSyntax conditional = ConditionalExpression(bstrNotZero, rosCreation, DefaultExpression(rosChar)); + ExpressionSyntax rosCreation = ObjectCreationExpression(rosChar).AddArgumentListArguments(Argument(bstrValue), Argument(length)); + ExpressionSyntax bstrNotNull = BinaryExpression(SyntaxKind.NotEqualsExpression, bstrValue, LiteralExpression(SyntaxKind.NullLiteralExpression)); + ExpressionSyntax conditional = ConditionalExpression(bstrNotNull, rosCreation, DefaultExpression(rosChar)); yield return ConversionOperatorDeclaration(Token(SyntaxKind.ImplicitKeyword), rosChar) .AddParameterListParameters(Parameter(bstrParam.Identifier).WithType(IdentifierName("BSTR"))) .WithExpressionBody(ArrowExpressionClause(conditional)) From 0b3a10e32b91a350449ff6ca541e4f98b799cbe7 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 16 Mar 2021 09:41:42 -0600 Subject: [PATCH 4/7] Fix inline arrays typed as function pointers --- src/Microsoft.Windows.CsWin32/Generator.cs | 10 ++++++++-- test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 4162def5..5b89fa6f 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -3650,13 +3650,19 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter // /// Always 8. // internal int Length => 8; // ... - IdentifierNameSyntax fixedLengthStructName = IdentifierName($"__{elementType.ToString().Replace('.', '_').Replace(':', '_')}_{length}"); + IdentifierNameSyntax fixedLengthStructName = IdentifierName($"__{elementType.ToString().Replace('.', '_').Replace(':', '_').Replace('*', '_').Replace('<', '_').Replace('>', '_').Replace('[', '_').Replace(']', '_').Replace(',', '_')}_{length}"); + SyntaxTokenList fieldModifiers = TokenList(Token(this.Visibility)); + if (RequiresUnsafe(elementType)) + { + fieldModifiers = fieldModifiers.Add(Token(SyntaxKind.UnsafeKeyword)); + } + var fixedLengthStruct = StructDeclaration(fixedLengthStructName.Identifier) .AddModifiers(Token(this.Visibility)) .AddMembers( FieldDeclaration(VariableDeclaration(elementType) .AddVariables(Enumerable.Range(0, length).Select(n => VariableDeclarator($"_{n}")).ToArray())) - .AddModifiers(Token(this.Visibility)), + .WithModifiers(fieldModifiers), PropertyDeclaration(PredefinedType(Token(SyntaxKind.IntKeyword)), "Length") .AddModifiers(Token(this.Visibility)) .WithExpressionBody(ArrowExpressionClause(lengthLiteralSyntax)) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index acf32c03..a13b4df7 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -134,6 +134,7 @@ public void InterestingAPIs( "WIA_CATEGORY_FINISHED_FILE", // GUID constant "DEVPKEY_MTPBTH_IsConnected", // PROPERTYKEY constant "IOleUILinkContainerW", // An IUnknown-derived interface with no GUID + "RTM_ENTITY_EXPORT_METHODS", "FILE_TYPE_NOTIFICATION_INPUT", "DS_SELECTION_LIST", // A struct with a fixed-length inline array of potentially managed structs "ISpellCheckerFactory", // COM interface that includes `ref` parameters From 3efdf852799d077cef18c42b202b0cda39a04993 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 16 Mar 2021 10:18:56 -0600 Subject: [PATCH 5/7] Support PCWSTR constants --- src/Microsoft.Windows.CsWin32/Generator.cs | 31 +++++++++++++++++-- .../GeneratorTests.cs | 1 + 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 5b89fa6f..0cee56a6 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -109,6 +109,12 @@ public class Generator : IDisposable "PSTR", }; + private static readonly HashSet SpecialTypeDefNames = new HashSet(StringComparer.Ordinal) + { + "PCWSTR", + "PCSTR", + }; + /// /// This is the preferred capitalizations for modules and class names. /// If they are not in this list, the capitalization will come from the metadata assembly. @@ -2179,6 +2185,7 @@ private FieldDeclarationSyntax DeclareField(FieldDefinitionHandle fieldDefHandle this.FindInteropDecorativeAttribute(customAttributes, nameof(GuidAttribute)) is CustomAttribute guidAttribute ? GuidValue(guidAttribute) : this.FindInteropDecorativeAttribute(customAttributes, "PropertyKeyAttribute") is CustomAttribute propertyKeyAttribute ? PropertyKeyValue(propertyKeyAttribute) : throw new NotSupportedException("Unsupported constant: " + name); + bool requiresUnsafe = false; if (fieldType.Type is not PredefinedTypeSyntax && value is not ObjectCreationExpressionSyntax) { if (fieldType.Type is IdentifierNameSyntax { Identifier: { ValueText: string typeName } } && this.TryGetHandleReleaseMethod(typeName, out _)) @@ -2186,6 +2193,11 @@ private FieldDeclarationSyntax DeclareField(FieldDefinitionHandle fieldDefHandle // Cast to IntPtr first, then the actual handle struct. value = CastExpression(fieldType.Type, CastExpression(IntPtrTypeSyntax, ParenthesizedExpression(value))); } + else if (fieldType.Type is IdentifierNameSyntax { Identifier: { ValueText: "PCWSTR" } }) + { + value = CastExpression(PointerType(PredefinedType(Token(SyntaxKind.CharKeyword))), ParenthesizedExpression(value)); + requiresUnsafe = true; + } else { value = CastExpression(fieldType.Type, ParenthesizedExpression(value)); @@ -2202,6 +2214,11 @@ private FieldDeclarationSyntax DeclareField(FieldDefinitionHandle fieldDefHandle modifiers = modifiers.Add(Token(SyntaxKind.ConstKeyword)); } + if (requiresUnsafe) + { + modifiers = modifiers.Add(Token(SyntaxKind.UnsafeKeyword)); + } + var result = FieldDeclaration(VariableDeclaration(fieldType.Type).AddVariables( VariableDeclarator(name).WithInitializer(EqualsValueClause(value)))) .WithModifiers(modifiers); @@ -3794,10 +3811,18 @@ void AddExtensionMethod(MethodDeclarationSyntax extensionMethod) private bool IsTypeDefStruct(IdentifierNameSyntax? identifierName) { - if (identifierName is object && this.typesByName.TryGetValue(identifierName.Identifier.ValueText, out TypeDefinitionHandle value)) + if (identifierName is object) { - TypeDefinition typeDef = this.mr.GetTypeDefinition(value); - return this.IsTypeDefStruct(typeDef); + if (this.typesByName.TryGetValue(identifierName.Identifier.ValueText, out TypeDefinitionHandle value)) + { + TypeDefinition typeDef = this.mr.GetTypeDefinition(value); + return this.IsTypeDefStruct(typeDef); + } + + if (SpecialTypeDefNames.Contains(identifierName.Identifier.ValueText)) + { + return true; + } } return false; diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index a13b4df7..6668abc7 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -133,6 +133,7 @@ public void InterestingAPIs( "JsVariantToValue", "WIA_CATEGORY_FINISHED_FILE", // GUID constant "DEVPKEY_MTPBTH_IsConnected", // PROPERTYKEY constant + "RT_CURSOR", // PCWSTR constant "IOleUILinkContainerW", // An IUnknown-derived interface with no GUID "RTM_ENTITY_EXPORT_METHODS", "FILE_TYPE_NOTIFICATION_INPUT", From a3e64207eacad6a05d2c4911b72da45d3845abca Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 16 Mar 2021 10:33:46 -0600 Subject: [PATCH 6/7] Expose `int` value behind BOOL Also adds supports for BOOL constants. Closes #186 --- src/Microsoft.Windows.CsWin32/Generator.cs | 22 +++++++++++++++---- .../GeneratorTests.cs | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 0cee56a6..1b54adbc 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -3048,8 +3048,8 @@ private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) MemberAccessExpressionSyntax fieldAccessExpression = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("value")); // Add property accessor - members = members.Add(PropertyDeclaration(PredefinedType(Token(SyntaxKind.BoolKeyword)), "Value") - .WithExpressionBody(ArrowExpressionClause(BinaryExpression(SyntaxKind.NotEqualsExpression, fieldAccessExpression, LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))))).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) + members = members.Add(PropertyDeclaration(PredefinedType(Token(SyntaxKind.IntKeyword)), "Value") + .WithExpressionBody(ArrowExpressionClause(fieldAccessExpression)).WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .AddModifiers(Token(this.Visibility))); // BOOL(bool value) => this.value = value ? 1 : 0; @@ -3061,10 +3061,17 @@ private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) .WithExpressionBody(ArrowExpressionClause(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, fieldAccessExpression, boolToInt))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); - // public static implicit operator bool(BOOL value) => value.Value; + // BOOL(int value) => this.value = value; + members = members.Add(ConstructorDeclaration(name.Identifier) + .AddModifiers(Token(this.Visibility)) + .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(PredefinedType(Token(SyntaxKind.IntKeyword)))) + .WithExpressionBody(ArrowExpressionClause(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, fieldAccessExpression, valueParameter))) + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); + + // public static implicit operator bool(BOOL value) => value.value != 0 ? true : false; members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ImplicitKeyword), PredefinedType(Token(SyntaxKind.BoolKeyword))) .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(name)) - .WithExpressionBody(ArrowExpressionClause(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueParameter, IdentifierName(fieldName)))) + .WithExpressionBody(ArrowExpressionClause(BinaryExpression(SyntaxKind.NotEqualsExpression, MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueParameter, IdentifierName(fieldName)), LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))))) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); @@ -3075,6 +3082,13 @@ private StructDeclarationSyntax DeclareTypeDefBOOLStruct(TypeDefinition typeDef) .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); + // public static explicit operator BOOL(int value) => new BOOL(value); + members = members.Add(ConversionOperatorDeclaration(Token(SyntaxKind.ExplicitKeyword), name) + .AddParameterListParameters(Parameter(valueParameter.Identifier).WithType(PredefinedType(Token(SyntaxKind.IntKeyword)))) + .WithExpressionBody(ArrowExpressionClause(ObjectCreationExpression(name).AddArgumentListArguments(Argument(valueParameter)))) + .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)) // operators MUST be public + .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))); + StructDeclarationSyntax result = StructDeclaration(name.Identifier) .WithMembers(members) .WithModifiers(TokenList(Token(this.Visibility), Token(SyntaxKind.ReadOnlyKeyword), Token(SyntaxKind.PartialKeyword))); diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 6668abc7..87fdecca 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -134,6 +134,7 @@ public void InterestingAPIs( "WIA_CATEGORY_FINISHED_FILE", // GUID constant "DEVPKEY_MTPBTH_IsConnected", // PROPERTYKEY constant "RT_CURSOR", // PCWSTR constant + "TRUE", // BOOL constant "IOleUILinkContainerW", // An IUnknown-derived interface with no GUID "RTM_ENTITY_EXPORT_METHODS", "FILE_TYPE_NOTIFICATION_INPUT", From c087ea21a35538175c2bb70f670b9d2729b3e6f8 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 16 Mar 2021 16:15:55 -0600 Subject: [PATCH 7/7] Workaround https://github.com/microsoft/win32metadata/issues/365 --- test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs index 87fdecca..d40cad19 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -65,7 +65,7 @@ public void Dispose() } [Theory] - [InlineData("CREATE_ALWAYS", "FILE_CREATE_FLAGS")] + [InlineData("COPYFILE2_CALLBACK_NONE", "COPYFILE2_MESSAGE_TYPE")] [InlineData("RTL_RUN_ONCE_ASYNC", null)] [InlineData("__zz__not_defined", null)] public void TryGetEnumName(string candidate, string? declaringEnum)