Skip to content

Commit 9059d51

Browse files
authored
Merge pull request #1127 from microsoft/flexibleArray
Always use pointers when referencing structs with variable-length inline arrays
2 parents a7a08ba + fd45c48 commit 9059d51

File tree

7 files changed

+87
-2
lines changed

7 files changed

+87
-2
lines changed

src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,16 @@ private IEnumerable<MethodDeclarationSyntax> DeclareFriendlyOverloads(MethodDefi
103103

104104
TypeHandleInfo parameterTypeInfo = originalSignature.ParameterTypes[param.SequenceNumber - 1];
105105
bool isManagedParameterType = this.IsManagedType(parameterTypeInfo);
106+
bool mustRemainAsPointer = parameterTypeInfo is PointerTypeHandleInfo { ElementType: HandleTypeHandleInfo pointedElement } && this.IsStructWithFlexibleArray(pointedElement);
107+
106108
IdentifierNameSyntax origName = IdentifierName(externParam.Identifier.ValueText);
107109

108-
if (isReserved && !isOut)
110+
if (mustRemainAsPointer)
111+
{
112+
// This block intentionally left blank, so as to disable further processing that might try to
113+
// replace a pointer with a `ref` or similar modifier.
114+
}
115+
else if (isReserved && !isOut)
109116
{
110117
// Remove the parameter and supply the default value for the type to the extern method.
111118
arguments[param.SequenceNumber - 1] = Argument(LiteralExpression(SyntaxKind.DefaultLiteralExpression));

src/Microsoft.Windows.CsWin32/Generator.Invariants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public partial class Generator
7777
private const string AlsoUsableForAttribute = "AlsoUsableForAttribute";
7878
private const string InvalidHandleValueAttribute = "InvalidHandleValueAttribute";
7979
private const string CanReturnMultipleSuccessValuesAttribute = "CanReturnMultipleSuccessValuesAttribute";
80+
private const string FlexibleArrayAttribute = "FlexibleArrayAttribute";
8081
private const string CanReturnErrorsAsSuccessAttribute = "CanReturnErrorsAsSuccessAttribute";
8182
private const string SimpleFileNameAnnotation = "SimpleFileName";
8283
private const string NamespaceContainerAnnotation = "NamespaceContainer";

src/Microsoft.Windows.CsWin32/Generator.MetadataHelpers.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ internal bool TryGetTypeDefHandle(string @namespace, string name, out TypeDefini
8484
return false;
8585
}
8686

87+
internal bool TryGetTypeDefHandle(EntityHandle entityHandle, out QualifiedTypeDefinitionHandle typeDefHandle)
88+
{
89+
if (entityHandle.IsNil)
90+
{
91+
typeDefHandle = default;
92+
return false;
93+
}
94+
95+
switch (entityHandle.Kind)
96+
{
97+
case HandleKind.TypeReference:
98+
return this.TryGetTypeDefHandle((TypeReferenceHandle)entityHandle, out typeDefHandle);
99+
case HandleKind.TypeDefinition:
100+
typeDefHandle = new QualifiedTypeDefinitionHandle(this, (TypeDefinitionHandle)entityHandle);
101+
return true;
102+
default:
103+
typeDefHandle = default;
104+
return false;
105+
}
106+
}
107+
87108
internal bool IsNonCOMInterface(TypeDefinition interfaceTypeDef)
88109
{
89110
if (this.Reader.StringComparer.Equals(interfaceTypeDef.Name, "IUnknown"))
@@ -164,6 +185,27 @@ internal bool IsInterface(TypeReferenceHandle typeRefHandle)
164185

165186
internal bool IsDelegate(TypeDefinition typeDef) => (typeDef.Attributes & TypeAttributes.Class) == TypeAttributes.Class && typeDef.BaseType.Kind == HandleKind.TypeReference && this.Reader.StringComparer.Equals(this.Reader.GetTypeReference((TypeReferenceHandle)typeDef.BaseType).Name, nameof(MulticastDelegate));
166187

188+
internal bool IsStructWithFlexibleArray(HandleTypeHandleInfo typeInfo)
189+
{
190+
return this.TryGetTypeDefHandle(typeInfo.Handle, out QualifiedTypeDefinitionHandle typeHandle)
191+
&& typeHandle.Generator.IsStructWithFlexibleArray(typeHandle.DefinitionHandle);
192+
}
193+
194+
internal bool IsStructWithFlexibleArray(TypeDefinitionHandle typeDefHandle)
195+
{
196+
TypeDefinition typeDef = this.Reader.GetTypeDefinition(typeDefHandle);
197+
foreach (FieldDefinitionHandle fieldHandle in typeDef.GetFields())
198+
{
199+
FieldDefinition field = this.Reader.GetFieldDefinition(fieldHandle);
200+
if (MetadataUtilities.FindAttribute(this.Reader, field.GetCustomAttributes(), InteropDecorationNamespace, FlexibleArrayAttribute) is not null)
201+
{
202+
return true;
203+
}
204+
}
205+
206+
return false;
207+
}
208+
167209
internal bool IsManagedType(TypeHandleInfo typeHandleInfo)
168210
{
169211
TypeHandleInfo elementType =

src/Microsoft.Windows.CsWin32/Generator.Struct.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ private StructDeclarationSyntax DeclareStruct(TypeDefinitionHandle typeDefHandle
2626
context = context with { AllowMarshaling = false };
2727
}
2828

29+
// If the last field has the [FlexibleArray] attribute, we must disable marshaling since the struct
30+
// is only ever valid when accessed via a pointer since the struct acts as a header of an arbitrarily-sized array.
31+
if (typeDef.GetFields().LastOrDefault() is FieldDefinitionHandle { IsNil: false } lastFieldHandle)
32+
{
33+
FieldDefinition lastField = this.Reader.GetFieldDefinition(lastFieldHandle);
34+
if (MetadataUtilities.FindAttribute(this.Reader, lastField.GetCustomAttributes(), InteropDecorationNamespace, FlexibleArrayAttribute) is not null)
35+
{
36+
context = context with { AllowMarshaling = false };
37+
}
38+
}
39+
2940
TypeSyntaxSettings typeSettings = context.Filter(this.fieldTypeSettings);
3041

3142
bool hasUtf16CharField = false;

src/Microsoft.Windows.CsWin32/PointerTypeHandleInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs
1818
}
1919

2020
bool xOptional = (parameterAttributes & ParameterAttributes.Optional) == ParameterAttributes.Optional;
21-
if (xOptional && forElement == Generator.GeneratingElement.InterfaceMember && nativeArrayInfo is null)
21+
bool mustUsePointers = xOptional && forElement == Generator.GeneratingElement.InterfaceMember && nativeArrayInfo is null;
22+
mustUsePointers |= this.ElementType is HandleTypeHandleInfo handleElementType && inputs.Generator?.IsStructWithFlexibleArray(handleElementType) is true;
23+
if (mustUsePointers)
2224
{
2325
// Disable marshaling because pointers to optional parameters cannot be passed by reference when used as parameters of a COM interface method.
2426
return new TypeSyntaxAndMarshaling(PointerType(this.ElementType.ToTypeSyntax(

test/Microsoft.Windows.CsWin32.Tests/COMTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,17 @@ public void ITypeNameBuilder_ToStringOverload(bool allowMarshaling)
380380
this.GenerateApi(typeName);
381381
}
382382

383+
[Fact]
384+
public void ReferencesToStructWithFlexibleArrayAreAlwaysPointers()
385+
{
386+
this.GenerateApi("IAMLine21Decoder");
387+
Assert.All(this.FindGeneratedMethod("SetOutputFormat"), m => Assert.IsType<PointerTypeSyntax>(m.ParameterList.Parameters[0].Type));
388+
389+
// Assert that the 'unmanaged' declaration of the struct is the *only* declaration.
390+
Assert.Single(this.FindGeneratedType("BITMAPINFO"));
391+
Assert.Empty(this.FindGeneratedType("BITMAPINFO_unmanaged"));
392+
}
393+
383394
[Theory]
384395
[CombinatorialData]
385396
public void COMInterfaceIIDInterfaceOnAppropriateTFMs(

test/Microsoft.Windows.CsWin32.Tests/ExternMethodTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ public void DefaultEntryPointIsNotEmitted()
7575
Assert.DoesNotContain(attribute.ArgumentList!.Arguments, a => a.NameEquals?.Name.Identifier.ValueText == "EntryPoint");
7676
}
7777

78+
[Fact]
79+
public void ReferencesToStructWithFlexibleArrayAreAlwaysPointers()
80+
{
81+
this.GenerateApi("CreateDIBSection");
82+
Assert.All(this.FindGeneratedMethod("CreateDIBSection"), m => Assert.IsType<PointerTypeSyntax>(m.ParameterList.Parameters[1].Type));
83+
84+
// Assert that the 'unmanaged' declaration of the struct is the *only* declaration.
85+
Assert.Single(this.FindGeneratedType("BITMAPINFO"));
86+
Assert.Empty(this.FindGeneratedType("BITMAPINFO_unmanaged"));
87+
}
88+
7889
private static AttributeSyntax? FindDllImportAttribute(SyntaxList<AttributeListSyntax> attributeLists) => attributeLists.SelectMany(al => al.Attributes).FirstOrDefault(a => a.Name.ToString() == "DllImport");
7990

8091
private IEnumerable<MethodDeclarationSyntax> GenerateMethod(string methodName)

0 commit comments

Comments
 (0)