From beac3ff5b37c8a6cafc2c9adb7e3974554b536a1 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 22 Feb 2024 15:37:06 -0700 Subject: [PATCH 1/5] Bump metadata versions: SDK: 59.0.13, WDK: 0.10.7 --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index df10ba52..a3bf50eb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,8 +4,8 @@ true true - 56.0.13-preview - 0.9.9-experimental + 59.0.13-preview + 0.10.7-experimental 0.1.42-alpha 4.8.0 From 678f4e7ab939fa033ae68110df6bb6fe69c60e1c Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 22 Feb 2024 17:06:46 -0700 Subject: [PATCH 2/5] Add support for pointer-typed constants --- .../Generator.Constant.cs | 34 ++++++++++++------- .../PointerTypeHandleInfo.cs | 2 +- .../ConstantsTests.cs | 1 + 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.Constant.cs b/src/Microsoft.Windows.CsWin32/Generator.Constant.cs index b0a3d9cb..5a2e011f 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Constant.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Constant.cs @@ -205,8 +205,9 @@ ReadOnlyMemory TrimCurlyBraces(ReadOnlyMemory arg) return argExpressions; } - private ObjectCreationExpressionSyntax? CreateConstantViaCtor(List> args, TypeSyntax targetType, TypeDefinition targetTypeDef) + private ObjectCreationExpressionSyntax? CreateConstantViaCtor(List> args, TypeSyntax targetType, TypeDefinition targetTypeDef, out bool unsafeRequired) { + unsafeRequired = false; foreach (MethodDefinitionHandle methodDefHandle in targetTypeDef.GetMethods()) { MethodDefinition methodDef = this.Reader.GetMethodDefinition(methodDefHandle); @@ -218,7 +219,8 @@ ReadOnlyMemory TrimCurlyBraces(ReadOnlyMemory arg) for (int i = 0; i < args.Count; i++) { TypeHandleInfo parameterTypeInfo = ctorSignature.ParameterTypes[i]; - argExpressions[i] = Argument(this.CreateConstant(args[i], parameterTypeInfo)); + argExpressions[i] = Argument(this.CreateConstant(args[i], parameterTypeInfo, out bool thisRequiresUnsafe)); + unsafeRequired |= thisRequiresUnsafe; i++; } @@ -229,8 +231,9 @@ ReadOnlyMemory TrimCurlyBraces(ReadOnlyMemory arg) return null; } - private ObjectCreationExpressionSyntax? CreateConstantByField(List> args, TypeSyntax targetType, TypeDefinition targetTypeDef) + private ObjectCreationExpressionSyntax? CreateConstantByField(List> args, TypeSyntax targetType, TypeDefinition targetTypeDef, out bool unsafeRequired) { + unsafeRequired = false; if (targetTypeDef.GetFields().Count != args.Count) { return null; @@ -246,7 +249,8 @@ ReadOnlyMemory TrimCurlyBraces(ReadOnlyMemory arg) fieldAssignmentExpressions[i] = AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName(fieldName), - this.CreateConstant(args[i], fieldTypeInfo)); + this.CreateConstant(args[i], fieldTypeInfo, out bool thisRequiresUnsafe)); + unsafeRequired |= thisRequiresUnsafe; i++; } @@ -255,18 +259,20 @@ ReadOnlyMemory TrimCurlyBraces(ReadOnlyMemory arg) .WithInitializer(InitializerExpression(SyntaxKind.ObjectInitializerExpression, SeparatedList()).AddExpressions(fieldAssignmentExpressions)); } - private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeHandleInfo targetType) + private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeHandleInfo targetType, out bool unsafeRequired) { + unsafeRequired = targetType is PointerTypeHandleInfo; return targetType switch { ArrayTypeHandleInfo { ElementType: PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.Byte } } pointerType => this.CreateByteArrayConstant(argsAsString), PrimitiveTypeHandleInfo primitiveType => ToExpressionSyntax(primitiveType.PrimitiveTypeCode, argsAsString), - HandleTypeHandleInfo handleType => this.CreateConstant(argsAsString, targetType.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, null).Type, (TypeReferenceHandle)handleType.Handle), + HandleTypeHandleInfo handleType => this.CreateConstant(argsAsString, targetType.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, null).Type, (TypeReferenceHandle)handleType.Handle, out unsafeRequired), + PointerTypeHandleInfo pointerType => CastExpression(pointerType.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, null).Type, ParenthesizedExpression(ToExpressionSyntax(PrimitiveTypeCode.UInt64, argsAsString))), _ => throw new GenerationFailedException($"Unsupported constant type: {targetType}"), }; } - private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeSyntax targetType, TypeReferenceHandle targetTypeRefHandle) + private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeSyntax targetType, TypeReferenceHandle targetTypeRefHandle, out bool unsafeRequired) { if (!this.TryGetTypeDefHandle(targetTypeRefHandle, out TypeDefinitionHandle targetTypeDefHandle)) { @@ -275,6 +281,7 @@ private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeS if (this.Reader.StringComparer.Equals(typeRef.Name, "Guid")) { List> guidArgs = SplitConstantArguments(argsAsString); + unsafeRequired = false; return this.CreateGuidConstant(guidArgs); } @@ -287,8 +294,8 @@ private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeS List> args = SplitConstantArguments(argsAsString); ObjectCreationExpressionSyntax? result = - this.CreateConstantViaCtor(args, targetType, typeDef) ?? - this.CreateConstantByField(args, targetType, typeDef); + this.CreateConstantViaCtor(args, targetType, typeDef, out unsafeRequired) ?? + this.CreateConstantByField(args, targetType, typeDef, out unsafeRequired); return result ?? throw new GenerationFailedException($"Unable to construct constant value given {args.Count} fields or constructor arguments."); } @@ -328,14 +335,15 @@ private ExpressionSyntax CreateGuidConstant(List> guidArgs) return ObjectCreationExpression(GuidTypeSyntax).AddArgumentListArguments(ctorArgs.Select(t => Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression, t))).ToArray()); } - private ExpressionSyntax CreateConstant(CustomAttribute constantAttribute, TypeHandleInfo targetType) + private ExpressionSyntax CreateConstant(CustomAttribute constantAttribute, TypeHandleInfo targetType, out bool unsafeRequired) { TypeReferenceHandle targetTypeRefHandle = (TypeReferenceHandle)((HandleTypeHandleInfo)targetType).Handle; CustomAttributeValue args = constantAttribute.DecodeValue(CustomAttributeTypeProvider.Instance); return this.CreateConstant( ((string)args.FixedArguments[0].Value!).AsMemory(), targetType.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, null).Type, - targetTypeRefHandle); + targetTypeRefHandle, + out unsafeRequired); } private FieldDeclarationSyntax DeclareConstant(FieldDefinition fieldDef) @@ -346,12 +354,12 @@ private FieldDeclarationSyntax DeclareConstant(FieldDefinition fieldDef) TypeHandleInfo fieldTypeInfo = fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null) with { IsConstantField = true }; CustomAttributeHandleCollection customAttributes = fieldDef.GetCustomAttributes(); TypeSyntaxAndMarshaling fieldType = fieldTypeInfo.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, customAttributes); + bool requiresUnsafe = false; ExpressionSyntax value = fieldDef.GetDefaultValue() is { IsNil: false } constantHandle ? ToExpressionSyntax(this.Reader, constantHandle) : this.FindInteropDecorativeAttribute(customAttributes, nameof(GuidAttribute)) is CustomAttribute guidAttribute ? GuidValue(guidAttribute) : - this.FindInteropDecorativeAttribute(customAttributes, "ConstantAttribute") is CustomAttribute constantAttribute ? this.CreateConstant(constantAttribute, fieldTypeInfo) : + this.FindInteropDecorativeAttribute(customAttributes, "ConstantAttribute") is CustomAttribute constantAttribute ? this.CreateConstant(constantAttribute, fieldTypeInfo, out requiresUnsafe) : throw new NotSupportedException("Unsupported constant: " + name); - bool requiresUnsafe = false; if (fieldType.Type is not PredefinedTypeSyntax && value is not ObjectCreationExpressionSyntax) { if (fieldTypeInfo is HandleTypeHandleInfo handleFieldTypeInfo && this.IsHandle(handleFieldTypeInfo.Handle, out _)) diff --git a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs index b4c00270..b49266f4 100644 --- a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs @@ -7,7 +7,7 @@ internal record PointerTypeHandleInfo(TypeHandleInfo ElementType) : TypeHandleIn { public override string ToString() => this.ToTypeSyntaxForDisplay().ToString(); - internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) + internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes = default) { Generator.NativeArrayInfo? nativeArrayInfo = customAttributes.HasValue ? inputs.Generator?.FindNativeArrayInfoAttribute(customAttributes.Value) : null; diff --git a/test/Microsoft.Windows.CsWin32.Tests/ConstantsTests.cs b/test/Microsoft.Windows.CsWin32.Tests/ConstantsTests.cs index 2a99c300..b7ba56b8 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/ConstantsTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/ConstantsTests.cs @@ -19,6 +19,7 @@ public ConstantsTests(ITestOutputHelper logger) [InlineData("X509_CERT")] // A constant defined as PCSTR [InlineData("RT_CURSOR")] // PCWSTR constant [InlineData("HBMMENU_POPUP_RESTORE")] // A HBITMAP handle as a constant + [InlineData("CONDITION_VARIABLE_INIT")] // A 0 constant typed void* public void InterestingConstants(string name) { From 782af0f9228f033d3994a998b0bb73f56207b874 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 22 Feb 2024 17:07:16 -0700 Subject: [PATCH 3/5] Improve error message to direct users to upgrade winmd references when necessary And also upgrade the one we use in tests. --- src/Microsoft.Windows.CsWin32/Generator.cs | 2 +- test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 38380313..7656b55f 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -1006,7 +1006,7 @@ internal void RequestInteropType(TypeReferenceHandle typeRefHandle, Context cont { AssemblyReference assemblyRef = this.Reader.GetAssemblyReference((AssemblyReferenceHandle)typeRef.ResolutionScope); string scope = this.Reader.GetString(assemblyRef.Name); - throw new GenerationFailedException($"Input metadata file \"{scope}\" has not been provided."); + throw new GenerationFailedException($"Input metadata file \"{scope}\" has not been provided, or is referenced at a version that is lacking the type \"{metadataName}\"."); } } } diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs index f05518b7..09b4a6d6 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs @@ -370,7 +370,7 @@ protected static class MyReferenceAssemblies { #pragma warning disable SA1202 // Elements should be ordered by access - because field initializer depend on each other private static readonly ImmutableArray AdditionalLegacyPackages = ImmutableArray.Create( - new PackageIdentity("Microsoft.Windows.SDK.Contracts", "10.0.22621.2")); + new PackageIdentity("Microsoft.Windows.SDK.Contracts", "10.0.22621.2428")); private static readonly ImmutableArray AdditionalModernPackages = AdditionalLegacyPackages.AddRange(ImmutableArray.Create( new PackageIdentity("System.Runtime.CompilerServices.Unsafe", "6.0.0"), From defa2a4e52d1160f440c1036229f931c5b344842 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 8 May 2024 13:02:05 -0600 Subject: [PATCH 4/5] Bump metadata to 60.0.34-preview --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index a3bf50eb..7a6dfc57 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,8 +4,8 @@ true true - 59.0.13-preview - 0.10.7-experimental + 60.0.34-preview + 0.11.4-experimental 0.1.42-alpha 4.8.0 From af6889155f1c25096265fc3bb981d0aea91f113f Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 9 May 2024 14:32:21 -0600 Subject: [PATCH 5/5] Add special handling for a few more release methods --- .../Generator.Handle.cs | 27 +++++++++++++++++++ .../HandleTests.cs | 9 +++++++ 2 files changed, 36 insertions(+) diff --git a/src/Microsoft.Windows.CsWin32/Generator.Handle.cs b/src/Microsoft.Windows.CsWin32/Generator.Handle.cs index 027174dd..cbd9dce1 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Handle.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Handle.cs @@ -149,6 +149,7 @@ public partial class Generator IdentifierName(renamedReleaseMethod ?? releaseMethod)), ArgumentList().AddArguments(releaseHandleArgument)); BlockSyntax? releaseBlock = null; + bool releaseMethodIsUnsafe = false; if (!(releaseMethodReturnType.Type is PredefinedTypeSyntax { Keyword: { RawKind: (int)SyntaxKind.BoolKeyword } } || releaseMethodReturnType.Type is QualifiedNameSyntax { Right: { Identifier: { ValueText: "BOOL" } } })) { @@ -197,6 +198,15 @@ public partial class Generator ExpressionSyntax noerror = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ParseName("global::Windows.Win32.Foundation.WIN32_ERROR"), IdentifierName("NO_ERROR")); releaseInvocation = BinaryExpression(SyntaxKind.EqualsExpression, releaseInvocation, noerror); break; + case "CONFIGRET": + noerror = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ParseName("global::Windows.Win32.Devices.DeviceAndDriverInstallation.CONFIGRET"), IdentifierName("CR_SUCCESS")); + releaseInvocation = BinaryExpression(SyntaxKind.EqualsExpression, releaseInvocation, noerror); + break; + case "LRESULT" when releaseMethod == "ICClose": + this.TryGenerateConstantOrThrow("ICERR_OK"); + noerror = CastExpression(ParseName("global::Windows.Win32.Foundation.LRESULT"), MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("PInvoke"), IdentifierName("ICERR_OK"))); + releaseInvocation = BinaryExpression(SyntaxKind.EqualsExpression, releaseInvocation, noerror); + break; case "HGLOBAL": case "HLOCAL": releaseInvocation = BinaryExpression(SyntaxKind.EqualsExpression, releaseInvocation, DefaultExpression(releaseMethodReturnType.Type)); @@ -205,12 +215,29 @@ public partial class Generator throw new NotSupportedException($"Return type {identifierName.Identifier.ValueText} on release method {releaseMethod} not supported."); } + break; + case PointerTypeSyntax { ElementType: PredefinedTypeSyntax elementType }: + releaseMethodIsUnsafe = true; + switch (elementType.Keyword.Kind()) + { + case SyntaxKind.VoidKeyword when releaseMethod == "FreeSid": + releaseInvocation = BinaryExpression(SyntaxKind.EqualsExpression, releaseInvocation, LiteralExpression(SyntaxKind.NullLiteralExpression)); + break; + default: + throw new NotSupportedException($"Return type {elementType}* on release method {releaseMethod} not supported."); + } + break; } } MethodDeclarationSyntax releaseHandleDeclaration = MethodDeclaration(PredefinedType(TokenWithSpace(SyntaxKind.BoolKeyword)), Identifier("ReleaseHandle")) .AddModifiers(TokenWithSpace(SyntaxKind.ProtectedKeyword), TokenWithSpace(SyntaxKind.OverrideKeyword)); + if (releaseMethodIsUnsafe) + { + releaseHandleDeclaration = releaseHandleDeclaration.AddModifiers(TokenWithSpace(SyntaxKind.UnsafeKeyword)); + } + releaseHandleDeclaration = releaseBlock is null ? releaseHandleDeclaration .WithExpressionBody(ArrowExpressionClause(releaseInvocation)) diff --git a/test/Microsoft.Windows.CsWin32.Tests/HandleTests.cs b/test/Microsoft.Windows.CsWin32.Tests/HandleTests.cs index 8c020e5d..375bc351 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/HandleTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/HandleTests.cs @@ -143,4 +143,13 @@ public void ReleaseMethodGeneratedWithHandleStruct() this.GenerateApi("HANDLE"); Assert.True(this.IsMethodGenerated("CloseHandle")); } + + [Theory] + [InlineData("ICOpen")] + [InlineData("CM_Register_Notification")] + [InlineData("AllocateAndInitializeSid")] + public void ReleaseMethodGeneratedWithUncommonReturnType(string api) + { + this.GenerateApi(api); + } }