From 18aafe6e3172df9d4883ada912b5f9b84b68df3b Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 9 Jan 2024 14:10:08 -0700 Subject: [PATCH 1/5] Fix parameter name in test class --- test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs index f6aff508..f05518b7 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTestBase.cs @@ -146,10 +146,10 @@ protected static bool IsOrContainsExternMethod(MethodDeclarationSyntax method) protected static IEnumerable FindAttribute(SyntaxList attributeLists, string name) => attributeLists.SelectMany(al => al.Attributes).Where(a => a.Name.ToString() == name); - protected void GenerateApi(string methodName) + protected void GenerateApi(string apiName) { this.generator ??= this.CreateGenerator(); - Assert.True(this.generator.TryGenerate(methodName, CancellationToken.None)); + Assert.True(this.generator.TryGenerate(apiName, CancellationToken.None)); this.CollectGeneratedCode(this.generator); this.AssertNoDiagnostics(); } From efcbb48396b0bca986777fa29086daa583850c76 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 9 Jan 2024 14:10:26 -0700 Subject: [PATCH 2/5] Add test for optional pointer parameters Repro for #1081 --- test/Microsoft.Windows.CsWin32.Tests/COMTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs b/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs index 979d6d51..88ebee56 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs @@ -267,6 +267,22 @@ public void InterestingComInterfaces( this.GenerateApi(api); } + /// + /// Verifies that COM methods that accept `[Optional, In]` parameters are declared as pointers + /// rather than `in` parameters, since the marshaller will throw NRE if the reference is null (via ). + /// + /// + [Fact] + public void OptionalInPointerParameterExposedAsPointer() + { + this.GenerateApi("IMMDevice"); + + MethodDeclarationSyntax comMethod = this.FindGeneratedMethod("Activate").First(m => !m.Modifiers.Any(SyntaxKind.StaticKeyword)); + ParameterSyntax optionalInParam = comMethod.ParameterList.Parameters[2]; + Assert.Empty(optionalInParam.Modifiers); + Assert.IsType(optionalInParam.Type); + } + [Fact] public void EnvironmentFailFast() { From 26d45cffb11b2c9918f8f108e4c05b139cd600ef Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 9 Jan 2024 14:42:27 -0700 Subject: [PATCH 3/5] Add `GeneratingElement` to ToTypeSyntax --- .../ArrayTypeHandleInfo.cs | 4 +- .../Generator.Com.cs | 12 +-- .../Generator.Constant.cs | 6 +- .../Generator.Delegate.cs | 6 +- .../Generator.Enum.cs | 2 +- .../Generator.Extern.cs | 6 +- .../Generator.FriendlyOverloads.cs | 2 +- .../Generator.Handle.cs | 6 +- .../Generator.InlineArrays.cs | 2 +- .../Generator.MetadataHelpers.cs | 2 +- .../Generator.Struct.cs | 4 +- .../Generator.TypeDef.cs | 2 +- src/Microsoft.Windows.CsWin32/Generator.cs | 76 ++++++++++++++++++- .../HandleTypeHandleInfo.cs | 2 +- .../PointerTypeHandleInfo.cs | 4 +- .../PrimitiveTypeHandleInfo.cs | 2 +- .../TypeHandleInfo.cs | 4 +- 17 files changed, 105 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/ArrayTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/ArrayTypeHandleInfo.cs index 46b2047c..a42d1950 100644 --- a/src/Microsoft.Windows.CsWin32/ArrayTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/ArrayTypeHandleInfo.cs @@ -7,9 +7,9 @@ internal record ArrayTypeHandleInfo(TypeHandleInfo ElementType, ArrayShape Shape { public override string ToString() => this.ToTypeSyntaxForDisplay().ToString(); - internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) + internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) { - TypeSyntaxAndMarshaling element = this.ElementType.ToTypeSyntax(inputs, customAttributes); + TypeSyntaxAndMarshaling element = this.ElementType.ToTypeSyntax(inputs, forElement, customAttributes); if (inputs.AllowMarshaling || inputs.IsField) { ArrayTypeSyntax arrayType = ArrayType(element.Type, SingletonList(ArrayRankSpecifier().AddSizes(this.Shape.Sizes.Select(size => LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(size))).ToArray()))); diff --git a/src/Microsoft.Windows.CsWin32/Generator.Com.cs b/src/Microsoft.Windows.CsWin32/Generator.Com.cs index 0dea78b3..2bdb33b5 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Com.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Com.cs @@ -155,10 +155,10 @@ from property in methodsByMetadata.Key.GetDeclarableProperties(methodDefs, origi MethodSignature signature = methodDefinition.Method.DecodeSignature(SignatureHandleProvider.Instance, null); CustomAttributeHandleCollection? returnTypeAttributes = methodDefinition.Generator.GetReturnTypeCustomAttributes(methodDefinition.Method); - TypeSyntax returnType = signature.ReturnType.ToTypeSyntax(typeSettings, returnTypeAttributes).Type; + TypeSyntax returnType = signature.ReturnType.ToTypeSyntax(typeSettings, GeneratingElement.InterfaceAsStructMember, returnTypeAttributes).Type; TypeSyntax returnTypePreserveSig = returnType; - ParameterListSyntax parameterList = methodDefinition.Generator.CreateParameterList(methodDefinition.Method, signature, typeSettings); + ParameterListSyntax parameterList = methodDefinition.Generator.CreateParameterList(methodDefinition.Method, signature, typeSettings with { Generator = methodDefinition.Generator }, GeneratingElement.InterfaceAsStructMember); ParameterListSyntax parameterListPreserveSig = parameterList; // preserve a copy that has no mutations. bool requiresMarshaling = parameterList.Parameters.Any(p => p.AttributeLists.SelectMany(al => al.Attributes).Any(a => a.Name is IdentifierNameSyntax { Identifier.ValueText: "MarshalAs" }) || p.Modifiers.Any(SyntaxKind.RefKeyword) || p.Modifiers.Any(SyntaxKind.OutKeyword) || p.Modifiers.Any(SyntaxKind.InKeyword)); FunctionPointerParameterListSyntax funcPtrParameters = FunctionPointerParameterList() @@ -596,7 +596,7 @@ static ExpressionSyntax ThisPointer(PointerTypeSyntax? typedPointer = null) else { baseTypeHandle.Generator.RequestInteropType(baseTypeHandle.DefinitionHandle, context); - TypeSyntax baseTypeSyntax = new HandleTypeHandleInfo(baseTypeHandle.Reader, baseTypeHandle.DefinitionHandle).ToTypeSyntax(this.comSignatureTypeSettings, null).Type; + TypeSyntax baseTypeSyntax = new HandleTypeHandleInfo(baseTypeHandle.Reader, baseTypeHandle.DefinitionHandle).ToTypeSyntax(this.comSignatureTypeSettings, GeneratingElement.InterfaceMember, null).Type; if (interfaceAsSubtype) { baseTypeSyntax = QualifiedName( @@ -666,11 +666,11 @@ static ExpressionSyntax ThisPointer(PointerTypeSyntax? typedPointer = null) MethodSignature signature = methodDefinition.DecodeSignature(SignatureHandleProvider.Instance, null); CustomAttributeHandleCollection? returnTypeAttributes = this.GetReturnTypeCustomAttributes(methodDefinition); - TypeSyntaxAndMarshaling returnTypeDetails = signature.ReturnType.ToTypeSyntax(typeSettings, returnTypeAttributes); + TypeSyntaxAndMarshaling returnTypeDetails = signature.ReturnType.ToTypeSyntax(typeSettings, GeneratingElement.InterfaceMember, returnTypeAttributes); TypeSyntax returnType = returnTypeDetails.Type; AttributeSyntax? returnsAttribute = MarshalAs(returnTypeDetails.MarshalAsAttribute, returnTypeDetails.NativeArrayInfo); - ParameterListSyntax? parameterList = this.CreateParameterList(methodDefinition, signature, this.comSignatureTypeSettings); + ParameterListSyntax? parameterList = this.CreateParameterList(methodDefinition, signature, this.comSignatureTypeSettings, GeneratingElement.InterfaceMember); bool preserveSig = interfaceAsSubtype || this.UsePreserveSigForComMethod(methodDefinition, signature, actualIfaceName, methodName); if (!preserveSig) @@ -972,7 +972,7 @@ private bool TryGetPropertyAccessorInfo(MethodDefinition methodDefinition, strin Parameter propertyTypeParameter = this.Reader.GetParameter(parameters.Skip(1).Single()); TypeHandleInfo propertyTypeInfo = signature.ParameterTypes[0]; - propertyType = propertyTypeInfo.ToTypeSyntax(syntaxSettings, propertyTypeParameter.GetCustomAttributes(), propertyTypeParameter.Attributes).Type; + propertyType = propertyTypeInfo.ToTypeSyntax(syntaxSettings, GeneratingElement.Property, propertyTypeParameter.GetCustomAttributes(), propertyTypeParameter.Attributes).Type; if (isGetter) { diff --git a/src/Microsoft.Windows.CsWin32/Generator.Constant.cs b/src/Microsoft.Windows.CsWin32/Generator.Constant.cs index cfb623e3..b0a3d9cb 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Constant.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Constant.cs @@ -261,7 +261,7 @@ private ExpressionSyntax CreateConstant(ReadOnlyMemory argsAsString, TypeH { 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, null).Type, (TypeReferenceHandle)handleType.Handle), + HandleTypeHandleInfo handleType => this.CreateConstant(argsAsString, targetType.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, null).Type, (TypeReferenceHandle)handleType.Handle), _ => throw new GenerationFailedException($"Unsupported constant type: {targetType}"), }; } @@ -334,7 +334,7 @@ private ExpressionSyntax CreateConstant(CustomAttribute constantAttribute, TypeH CustomAttributeValue args = constantAttribute.DecodeValue(CustomAttributeTypeProvider.Instance); return this.CreateConstant( ((string)args.FixedArguments[0].Value!).AsMemory(), - targetType.ToTypeSyntax(this.fieldTypeSettings, null).Type, + targetType.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, null).Type, targetTypeRefHandle); } @@ -345,7 +345,7 @@ 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, customAttributes); + TypeSyntaxAndMarshaling fieldType = fieldTypeInfo.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.Constant, customAttributes); ExpressionSyntax value = fieldDef.GetDefaultValue() is { IsNil: false } constantHandle ? ToExpressionSyntax(this.Reader, constantHandle) : this.FindInteropDecorativeAttribute(customAttributes, nameof(GuidAttribute)) is CustomAttribute guidAttribute ? GuidValue(guidAttribute) : diff --git a/src/Microsoft.Windows.CsWin32/Generator.Delegate.cs b/src/Microsoft.Windows.CsWin32/Generator.Delegate.cs index 5b54dd54..7600aba5 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Delegate.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Delegate.cs @@ -54,10 +54,10 @@ private DelegateDeclarationSyntax DeclareDelegate(TypeDefinition typeDef) } this.GetSignatureForDelegate(typeDef, out MethodDefinition invokeMethodDef, out MethodSignature signature, out CustomAttributeHandleCollection? returnTypeAttributes); - TypeSyntaxAndMarshaling returnValue = signature.ReturnType.ToTypeSyntax(typeSettings, returnTypeAttributes); + TypeSyntaxAndMarshaling returnValue = signature.ReturnType.ToTypeSyntax(typeSettings, GeneratingElement.Delegate, returnTypeAttributes); DelegateDeclarationSyntax result = DelegateDeclaration(returnValue.Type, Identifier(name)) - .WithParameterList(FixTrivia(this.CreateParameterList(invokeMethodDef, signature, typeSettings))) + .WithParameterList(FixTrivia(this.CreateParameterList(invokeMethodDef, signature, typeSettings, GeneratingElement.Delegate))) .AddModifiers(TokenWithSpace(this.Visibility), TokenWithSpace(SyntaxKind.UnsafeKeyword)); result = returnValue.AddReturnMarshalAs(result); @@ -149,6 +149,6 @@ private FunctionPointerParameterSyntax TranslateDelegateToFunctionPointer(TypeHa return FunctionPointerParameter(delegateTypeDef.Generator.FunctionPointer(delegateTypeDef.Definition)); } - return FunctionPointerParameter(parameterTypeInfo.ToTypeSyntax(this.functionPointerTypeSettings, customAttributeHandles).GetUnmarshaledType()); + return FunctionPointerParameter(parameterTypeInfo.ToTypeSyntax(this.functionPointerTypeSettings, GeneratingElement.FunctionPointer, customAttributeHandles).GetUnmarshaledType()); } } diff --git a/src/Microsoft.Windows.CsWin32/Generator.Enum.cs b/src/Microsoft.Windows.CsWin32/Generator.Enum.cs index b1ee1920..a710a5b0 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Enum.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Enum.cs @@ -21,7 +21,7 @@ private EnumDeclarationSyntax DeclareEnum(TypeDefinition typeDef) ConstantHandle valueHandle = fieldDef.GetDefaultValue(); if (valueHandle.IsNil) { - enumBaseType = fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null).ToTypeSyntax(this.enumTypeSettings, null).Type; + enumBaseType = fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null).ToTypeSyntax(this.enumTypeSettings, GeneratingElement.EnumValue, null).Type; continue; } diff --git a/src/Microsoft.Windows.CsWin32/Generator.Extern.cs b/src/Microsoft.Windows.CsWin32/Generator.Extern.cs index b77bcb5e..27b3f2c1 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Extern.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Extern.cs @@ -196,7 +196,7 @@ private void DeclareExternMethod(MethodDefinitionHandle methodDefinitionHandle) bool requiresUnicodeCharSet = signature.ParameterTypes.Any(p => p is PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.Char }); CustomAttributeHandleCollection? returnTypeAttributes = this.GetReturnTypeCustomAttributes(methodDefinition); - TypeSyntaxAndMarshaling returnType = signature.ReturnType.ToTypeSyntax(typeSettings, returnTypeAttributes, ParameterAttributes.Out); + TypeSyntaxAndMarshaling returnType = signature.ReturnType.ToTypeSyntax(typeSettings, GeneratingElement.ExternMethod, returnTypeAttributes, ParameterAttributes.Out); // Search for any enum substitutions. TypeSyntax? returnTypeEnumName = this.FindAssociatedEnum(returnTypeAttributes); @@ -231,7 +231,7 @@ AttributeListSyntax CreateDllImportAttributeList() => AttributeList() explicitInterfaceSpecifier: null!, SafeIdentifier(methodName), null!, - this.CreateParameterList(methodDefinition, signature, typeSettings), + this.CreateParameterList(methodDefinition, signature, typeSettings, GeneratingElement.ExternMethod), List(), body: null!, TokenWithLineFeed(SyntaxKind.SemicolonToken)); @@ -259,7 +259,7 @@ AttributeListSyntax CreateDllImportAttributeList() => AttributeList() { string ns = this.GetMethodNamespace(methodDefinition); NameSyntax nsSyntax = ParseName(ReplaceCommonNamespaceWithAlias(this, ns)); - ParameterListSyntax exposedParameterList = this.CreateParameterList(methodDefinition, signature, typeSettings); + ParameterListSyntax exposedParameterList = this.CreateParameterList(methodDefinition, signature, typeSettings, GeneratingElement.ExternMethod); static SyntaxToken RefInOutKeyword(ParameterSyntax p) => p.Modifiers.Any(SyntaxKind.OutKeyword) ? TokenWithSpace(SyntaxKind.OutKeyword) : p.Modifiers.Any(SyntaxKind.RefKeyword) ? TokenWithSpace(SyntaxKind.RefKeyword) : diff --git a/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs b/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs index 04d0c311..d2e2c5dd 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs @@ -126,7 +126,7 @@ private IEnumerable DeclareFriendlyOverloads(MethodDefi .WithModifiers(TokenList(TokenWithSpace(SyntaxKind.OutKeyword))); // HANDLE SomeLocal; - leadingStatements.Add(LocalDeclarationStatement(VariableDeclaration(pointedElementInfo.ToTypeSyntax(parameterTypeSyntaxSettings, null).Type).AddVariables( + leadingStatements.Add(LocalDeclarationStatement(VariableDeclaration(pointedElementInfo.ToTypeSyntax(parameterTypeSyntaxSettings, GeneratingElement.FriendlyOverload, null).Type).AddVariables( VariableDeclarator(typeDefHandleName.Identifier)))); // Argument: &SomeLocal diff --git a/src/Microsoft.Windows.CsWin32/Generator.Handle.cs b/src/Microsoft.Windows.CsWin32/Generator.Handle.cs index 11fde450..6a6ffaa5 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Handle.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Handle.cs @@ -40,7 +40,7 @@ public partial class Generator MethodSignature releaseMethodSignature = releaseMethodDef.DecodeSignature(SignatureHandleProvider.Instance, null); TypeHandleInfo releaseMethodParameterTypeHandleInfo = releaseMethodSignature.ParameterTypes[0]; - TypeSyntaxAndMarshaling releaseMethodParameterType = releaseMethodParameterTypeHandleInfo.ToTypeSyntax(this.externSignatureTypeSettings, default); + TypeSyntaxAndMarshaling releaseMethodParameterType = releaseMethodParameterTypeHandleInfo.ToTypeSyntax(this.externSignatureTypeSettings, GeneratingElement.HelperClassMember, default); // If the release method takes more than one parameter, we can't generate a SafeHandle for it. if (releaseMethodSignature.RequiredParameterCount != 1) @@ -80,7 +80,7 @@ public partial class Generator IntPtr preferredInvalidValue = GetPreferredInvalidHandleValue(invalidHandleValues, new IntPtr(-1)); CustomAttributeHandleCollection? atts = this.GetReturnTypeCustomAttributes(releaseMethodDef); - TypeSyntaxAndMarshaling releaseMethodReturnType = releaseMethodSignature.ReturnType.ToTypeSyntax(this.externSignatureTypeSettings, atts); + TypeSyntaxAndMarshaling releaseMethodReturnType = releaseMethodSignature.ReturnType.ToTypeSyntax(this.externSignatureTypeSettings, GeneratingElement.HelperClassMember, atts); this.TryGetRenamedMethod(releaseMethod, out string? renamedReleaseMethod); @@ -134,7 +134,7 @@ public partial class Generator bool implicitConversion = typeDefStructFieldType is PrimitiveTypeHandleInfo { PrimitiveTypeCode: PrimitiveTypeCode.IntPtr } or PointerTypeHandleInfo; ArgumentSyntax releaseHandleArgument = Argument(CastExpression( releaseMethodParameterType.Type, - implicitConversion ? thisHandle : CheckedExpression(CastExpression(typeDefStructFieldType!.ToTypeSyntax(this.fieldTypeSettings, null).Type, CastExpression(IdentifierName("nint"), thisHandle))))); + implicitConversion ? thisHandle : CheckedExpression(CastExpression(typeDefStructFieldType!.ToTypeSyntax(this.fieldTypeSettings, GeneratingElement.HelperClassMember, null).Type, CastExpression(IdentifierName("nint"), thisHandle))))); // protected override bool ReleaseHandle() => ReleaseMethod((struct)this.handle); // Special case release functions based on their return type as follows: (https://github.com/microsoft/win32metadata/issues/25) diff --git a/src/Microsoft.Windows.CsWin32/Generator.InlineArrays.cs b/src/Microsoft.Windows.CsWin32/Generator.InlineArrays.cs index 0350ac88..7e3c3e41 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.InlineArrays.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.InlineArrays.cs @@ -722,7 +722,7 @@ StatementSyntax ClearSlice(ExpressionSyntax span) => } else { - qualifiedElementType = fieldTypeHandleInfo.ToTypeSyntax(this.extensionMethodSignatureTypeSettings, customAttributes).Type switch + qualifiedElementType = fieldTypeHandleInfo.ToTypeSyntax(this.extensionMethodSignatureTypeSettings, GeneratingElement.Other, customAttributes).Type switch { ArrayTypeSyntax at => at.ElementType, PointerTypeSyntax ptrType => ptrType.ElementType, diff --git a/src/Microsoft.Windows.CsWin32/Generator.MetadataHelpers.cs b/src/Microsoft.Windows.CsWin32/Generator.MetadataHelpers.cs index 597fbef3..5f9f5cf4 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.MetadataHelpers.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.MetadataHelpers.cs @@ -444,7 +444,7 @@ private bool IsManagedType(TypeDefinitionHandle typeDefinitionHandle) } catch (Exception ex) { - throw new GenerationFailedException($"Unable to determine if {new HandleTypeHandleInfo(this.Reader, typeDefinitionHandle).ToTypeSyntax(this.errorMessageTypeSettings, null)} is a managed type.", ex); + throw new GenerationFailedException($"Unable to determine if {new HandleTypeHandleInfo(this.Reader, typeDefinitionHandle).ToTypeSyntax(this.errorMessageTypeSettings, GeneratingElement.Other, null)} is a managed type.", ex); } } } diff --git a/src/Microsoft.Windows.CsWin32/Generator.Struct.cs b/src/Microsoft.Windows.CsWin32/Generator.Struct.cs index af754db7..4ca7c971 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.Struct.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.Struct.cs @@ -70,7 +70,7 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinitionHandle typeDefHandle } } - TypeSyntaxAndMarshaling fieldTypeSyntax = fieldTypeInfo.ToTypeSyntax(thisFieldTypeSettings, fieldAttributes); + TypeSyntaxAndMarshaling fieldTypeSyntax = fieldTypeInfo.ToTypeSyntax(thisFieldTypeSettings, GeneratingElement.StructMember, fieldAttributes); (TypeSyntax FieldType, SyntaxList AdditionalMembers, AttributeSyntax? MarshalAsAttribute) fieldInfo = this.ReinterpretFieldType(fieldDef, fieldTypeSyntax.Type, fieldAttributes, context); additionalMembers = additionalMembers.AddRange(fieldInfo.AdditionalMembers); @@ -195,7 +195,7 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinitionHandle typeDefHandle // then we must type it as an array. if (context.AllowMarshaling && fieldTypeHandleInfo is PointerTypeHandleInfo ptr3 && this.IsInterface(ptr3.ElementType)) { - return (ArrayType(ptr3.ElementType.ToTypeSyntax(typeSettings, null).Type).AddRankSpecifiers(ArrayRankSpecifier()), default(SyntaxList), marshalAs); + return (ArrayType(ptr3.ElementType.ToTypeSyntax(typeSettings, GeneratingElement.Field, null).Type).AddRankSpecifiers(ArrayRankSpecifier()), default(SyntaxList), marshalAs); } return (originalType, default(SyntaxList), marshalAs); diff --git a/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs b/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs index 85a2babe..1ec49a29 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.TypeDef.cs @@ -99,7 +99,7 @@ private StructDeclarationSyntax DeclareTypeDefStruct(TypeDefinition typeDef, Typ VariableDeclaratorSyntax fieldDeclarator = VariableDeclarator(fieldIdentifierName.Identifier); CustomAttributeHandleCollection fieldAttributes = fieldDef.GetCustomAttributes(); TypeHandleInfo fieldTypeInfo = fieldDef.DecodeSignature(SignatureHandleProvider.Instance, null); - TypeSyntaxAndMarshaling fieldType = fieldTypeInfo.ToTypeSyntax(typeSettings, fieldAttributes); + TypeSyntaxAndMarshaling fieldType = fieldTypeInfo.ToTypeSyntax(typeSettings, GeneratingElement.Field, fieldAttributes); (TypeSyntax FieldType, SyntaxList AdditionalMembers, AttributeSyntax? _) fieldInfo = this.ReinterpretFieldType(fieldDef, fieldType.Type, fieldAttributes, this.DefaultContext); SyntaxList members = List(); diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 47ebc3d8..9c424d4c 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -151,6 +151,74 @@ void AddSymbolIf(bool condition, string symbol) FetchTemplate("ComHelpers", this, out this.comHelperClass); } + internal enum GeneratingElement + { + /// + /// Any other member that isn't otherwise enumerated. + /// + Other, + + /// + /// A member on a COM interface that is actually being generated as an interface (as opposed to a struct for no-marshal COM). + /// + InterfaceMember, + + /// + /// A member on a COM interface that is declared as a struct instead of an interface to avoid the marshaler. + /// + InterfaceAsStructMember, + + /// + /// A delegate. + /// + Delegate, + + /// + /// An extern, static method. + /// + ExternMethod, + + /// + /// A property on a COM interface or struct. + /// + Property, + + /// + /// A field on a struct. + /// + Field, + + /// + /// A constant value. + /// + Constant, + + /// + /// A function pointer. + /// + FunctionPointer, + + /// + /// An enum value. + /// + EnumValue, + + /// + /// A friendly overload. + /// + FriendlyOverload, + + /// + /// A member on a helper class (e.g. a SafeHandle-derived class). + /// + HelperClassMember, + + /// + /// A member of a struct that does not stand for a COM interface. + /// + StructMember, + } + private enum Feature { /// @@ -1341,10 +1409,10 @@ private string GetNamespaceForPossiblyNestedType(TypeDefinition nestedTypeDef) } } - private ParameterListSyntax CreateParameterList(MethodDefinition methodDefinition, MethodSignature signature, TypeSyntaxSettings typeSettings) - => FixTrivia(ParameterList().AddParameters(methodDefinition.GetParameters().Select(this.Reader.GetParameter).Where(p => !p.Name.IsNil).Select(p => this.CreateParameter(signature.ParameterTypes[p.SequenceNumber - 1], p, typeSettings)).ToArray())); + private ParameterListSyntax CreateParameterList(MethodDefinition methodDefinition, MethodSignature signature, TypeSyntaxSettings typeSettings, GeneratingElement forElement) + => FixTrivia(ParameterList().AddParameters(methodDefinition.GetParameters().Select(this.Reader.GetParameter).Where(p => !p.Name.IsNil).Select(p => this.CreateParameter(signature.ParameterTypes[p.SequenceNumber - 1], p, typeSettings, forElement)).ToArray())); - private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter parameter, TypeSyntaxSettings typeSettings) + private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter parameter, TypeSyntaxSettings typeSettings, GeneratingElement forElement) { string name = this.Reader.GetString(parameter.Name); try @@ -1352,7 +1420,7 @@ private ParameterSyntax CreateParameter(TypeHandleInfo parameterInfo, Parameter // TODO: // * Notice [Out][RAIIFree] handle producing parameters. Can we make these provide SafeHandle's? bool isReturnOrOutParam = parameter.SequenceNumber == 0 || (parameter.Attributes & ParameterAttributes.Out) == ParameterAttributes.Out; - TypeSyntaxAndMarshaling parameterTypeSyntax = parameterInfo.ToTypeSyntax(typeSettings, parameter.GetCustomAttributes(), parameter.Attributes); + TypeSyntaxAndMarshaling parameterTypeSyntax = parameterInfo.ToTypeSyntax(typeSettings, forElement, parameter.GetCustomAttributes(), parameter.Attributes); // Determine the custom attributes to apply. AttributeListSyntax? attributes = AttributeList(); diff --git a/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs index 7e17b595..aa3b45ac 100644 --- a/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs @@ -41,7 +41,7 @@ internal bool IsType(string leafName) } } - internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes = default) + internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes = default) { NameSyntax? nameSyntax; bool isInterface; diff --git a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs index c2cfed32..feae1c12 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, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) + internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) { // We can't marshal a pointer exposed as a field, unless it's a pointer to an array. if (inputs.AllowMarshaling && inputs.IsField && (customAttributes is null || inputs.Generator?.FindNativeArrayInfoAttribute(customAttributes.Value) is null)) @@ -15,7 +15,7 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs inputs = inputs with { AllowMarshaling = false }; } - TypeSyntaxAndMarshaling elementTypeDetails = this.ElementType.ToTypeSyntax(inputs with { PreferInOutRef = false }, customAttributes); + TypeSyntaxAndMarshaling elementTypeDetails = this.ElementType.ToTypeSyntax(inputs with { PreferInOutRef = false }, forElement, customAttributes); bool xOptional = (parameterAttributes & ParameterAttributes.Optional) == ParameterAttributes.Optional; if (elementTypeDetails.MarshalAsAttribute is object || inputs.Generator?.IsManagedType(this.ElementType) is true || (inputs.PreferInOutRef && !xOptional && this.ElementType is PrimitiveTypeHandleInfo { PrimitiveTypeCode: not PrimitiveTypeCode.Void })) { diff --git a/src/Microsoft.Windows.CsWin32/PrimitiveTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/PrimitiveTypeHandleInfo.cs index f61d1351..13fba407 100644 --- a/src/Microsoft.Windows.CsWin32/PrimitiveTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/PrimitiveTypeHandleInfo.cs @@ -7,7 +7,7 @@ internal record PrimitiveTypeHandleInfo(PrimitiveTypeCode PrimitiveTypeCode) : T { public override string ToString() => this.ToTypeSyntaxForDisplay().ToString(); - internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) + internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) { // We want to expose the enum type when there is one, but doing it *properly* requires marshaling to the underlying type. // The length of the enum may differ from the length of the primitive type, which means we can't just use the enum type directly. diff --git a/src/Microsoft.Windows.CsWin32/TypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/TypeHandleInfo.cs index b937a84c..44c8c53e 100644 --- a/src/Microsoft.Windows.CsWin32/TypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/TypeHandleInfo.cs @@ -9,7 +9,7 @@ internal abstract record TypeHandleInfo internal bool IsConstantField { get; init; } - internal abstract TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes = default); + internal abstract TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes = default); internal abstract bool? IsValueType(TypeSyntaxSettings inputs); @@ -32,7 +32,7 @@ protected static bool TryGetSimpleName(TypeSyntax nameSyntax, [NotNullWhen(true) return true; } - protected TypeSyntax ToTypeSyntaxForDisplay() => this.ToTypeSyntax(DebuggerDisplaySettings, null).Type; + protected TypeSyntax ToTypeSyntaxForDisplay() => this.ToTypeSyntax(DebuggerDisplaySettings, Generator.GeneratingElement.Other, null).Type; protected Generator.Context GetContext(TypeSyntaxSettings inputs) => inputs.Generator is not null ? inputs.Generator.DefaultContext with { AllowMarshaling = inputs.AllowMarshaling } From bac41aeb58d49a4d53ebdce1a782f447a717dc94 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 9 Jan 2024 16:58:24 -0700 Subject: [PATCH 4/5] Stabilize test that depended on syntax order --- 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 488eedb2..248225e9 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/GeneratorTests.cs @@ -483,7 +483,7 @@ public void NativeArray_OfManagedTypes_MarshaledAsLPArray(bool allowMarshaling) this.CollectGeneratedCode(this.generator); this.AssertNoDiagnostics(); - var generatedMethod = this.FindGeneratedMethod("OMSetRenderTargets").Where(m => m.ParameterList.Parameters.Count == 3 && m.ParameterList.Parameters[0].Identifier.ValueText == "NumViews").FirstOrDefault(); + var generatedMethod = this.FindGeneratedMethod("OMSetRenderTargets").FirstOrDefault(m => m.ParameterList.Parameters.Count == 3 && m.ParameterList.Parameters[0].Identifier.ValueText == "NumViews" && (!allowMarshaling || m.Parent is InterfaceDeclarationSyntax)); Assert.NotNull(generatedMethod); if (allowMarshaling) From dcca27d49f1e2927f53110dae4b830f557318dda Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Tue, 9 Jan 2024 17:00:09 -0700 Subject: [PATCH 5/5] Avoid `ref` optional parameters on COM interface methods Fixes #1081 --- .../PointerTypeHandleInfo.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs index feae1c12..667b611c 100644 --- a/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs @@ -9,21 +9,37 @@ internal record PointerTypeHandleInfo(TypeHandleInfo ElementType) : TypeHandleIn internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs, Generator.GeneratingElement forElement, CustomAttributeHandleCollection? customAttributes, ParameterAttributes parameterAttributes) { + Generator.NativeArrayInfo? nativeArrayInfo = customAttributes.HasValue ? inputs.Generator?.FindNativeArrayInfoAttribute(customAttributes.Value) : null; + // We can't marshal a pointer exposed as a field, unless it's a pointer to an array. - if (inputs.AllowMarshaling && inputs.IsField && (customAttributes is null || inputs.Generator?.FindNativeArrayInfoAttribute(customAttributes.Value) is null)) + if (inputs.AllowMarshaling && inputs.IsField && (customAttributes is null || nativeArrayInfo is null)) { inputs = inputs with { AllowMarshaling = false }; } - TypeSyntaxAndMarshaling elementTypeDetails = this.ElementType.ToTypeSyntax(inputs with { PreferInOutRef = false }, forElement, customAttributes); bool xOptional = (parameterAttributes & ParameterAttributes.Optional) == ParameterAttributes.Optional; - if (elementTypeDetails.MarshalAsAttribute is object || inputs.Generator?.IsManagedType(this.ElementType) is true || (inputs.PreferInOutRef && !xOptional && this.ElementType is PrimitiveTypeHandleInfo { PrimitiveTypeCode: not PrimitiveTypeCode.Void })) + if (xOptional && forElement == Generator.GeneratingElement.InterfaceMember && nativeArrayInfo is null) + { + // Disable marshaling because pointers to optional parameters cannot be passed by reference when used as parameters of a COM interface method. + return new TypeSyntaxAndMarshaling(PointerType(this.ElementType.ToTypeSyntax( + inputs with + { + AllowMarshaling = false, + PreferInOutRef = false, + }, + forElement, + customAttributes, + parameterAttributes).Type)); + } + + TypeSyntaxAndMarshaling elementTypeDetails = this.ElementType.ToTypeSyntax(inputs with { PreferInOutRef = false }, forElement, customAttributes, parameterAttributes); + if (elementTypeDetails.MarshalAsAttribute is object || (inputs.Generator?.IsManagedType(this.ElementType) is true) || (inputs.PreferInOutRef && !xOptional && this.ElementType is PrimitiveTypeHandleInfo { PrimitiveTypeCode: not PrimitiveTypeCode.Void })) { bool xIn = (parameterAttributes & ParameterAttributes.In) == ParameterAttributes.In; bool xOut = (parameterAttributes & ParameterAttributes.Out) == ParameterAttributes.Out; // A pointer to a marshaled object is not allowed. - if (inputs.AllowMarshaling && customAttributes.HasValue && inputs.Generator?.FindNativeArrayInfoAttribute(customAttributes.Value) is { } nativeArrayInfo) + if (inputs.AllowMarshaling && customAttributes.HasValue && nativeArrayInfo is not null) { // But this pointer represents an array, so type as an array. MarshalAsAttribute marshalAsAttribute = new MarshalAsAttribute(UnmanagedType.LPArray);