From 9a5f66f15691cd7ff951a4dc9d4550447b8aab47 Mon Sep 17 00:00:00 2001 From: Jevan Saks Date: Mon, 24 Nov 2025 22:42:34 -0800 Subject: [PATCH 1/2] Project flexible arrays as Span --- .../Generator.FriendlyOverloads.cs | 21 +++++++++++++++---- .../CsWin32GeneratorTests.cs | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs b/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs index b98b7b6e..01c33b3f 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.FriendlyOverloads.cs @@ -198,6 +198,8 @@ private IEnumerable DeclareFriendlyOverload( bool isManagedParameterType = this.IsManagedType(parameterTypeInfo); MemorySize? memorySize = null; bool mustRemainAsPointer = false; + bool isArray = false; + bool projectAsSpanBytes = false; bool isPointerToStructWithFlexibleArray = parameterTypeInfo is PointerTypeHandleInfo { ElementType: HandleTypeHandleInfo pointedElement } && pointedElement.Generator.IsStructWithFlexibleArray(pointedElement); if (this.FindInteropDecorativeAttribute(paramAttributes, MemorySizeAttribute) is CustomAttribute memorySizeAttribute) { @@ -214,7 +216,20 @@ private IEnumerable DeclareFriendlyOverload( else if (memorySize is null) { // If there's no MemorySize attribute, we may still need to keep this parameter as a pointer if it's a struct with a flexible array. - mustRemainAsPointer = isPointerToStructWithFlexibleArray; + if (isPointerToStructWithFlexibleArray) + { + if (improvePointersToSpansAndRefs) + { + // FlexibleSizeArrays are easier to work with if they project as Span. Callers can request the pointer verions + // via FriendlyOverloads.IncludePointerOverloads = true. + isArray = true; + projectAsSpanBytes = true; + } + else + { + mustRemainAsPointer = true; + } + } } else if (!improvePointersToSpansAndRefs) { @@ -239,7 +254,6 @@ private IEnumerable DeclareFriendlyOverload( IdentifierNameSyntax origName = IdentifierName(externParam.Identifier.ValueText); - bool isArray = false; bool isNullTerminated = false; // TODO bool isCountOfBytes = false; short? countParamIndex = null; @@ -273,7 +287,6 @@ private IEnumerable DeclareFriendlyOverload( isCountOfBytes = true; } - bool projectAsSpanBytes = false; if (improvePointersToSpansAndRefs && IsVoidPtrOrPtrPtr(externParam.Type)) { // if it's memory-sized project as Span @@ -1002,7 +1015,7 @@ bool TryHandleCountParam(TypeSyntax elementType, bool nullableSource) if (externParam.Type is PointerTypeSyntax) { remainsRefType = false; - if (isCountOfBytes) + if (isCountOfBytes || isPointerToStructWithFlexibleArray) { // For parameters annotated as count of bytes, we need to switch the friendly parameter to Span // and then cast to (ParamType*) when we call the p/invoke. diff --git a/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs b/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs index 46d492ac..332fc3b2 100644 --- a/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs +++ b/test/CsWin32Generator.Tests/CsWin32GeneratorTests.cs @@ -209,6 +209,8 @@ public async Task PointerReturnValueIsPreserved() ["CancelIoEx", "CancelIoEx", "SafeHandle hFile, global::System.Threading.NativeOverlapped* lpOverlapped"], ["ITypeInfo", "GetNames", "this winmdroot.System.Com.ITypeInfo @this, int memid, Span rgBstrNames, out uint pcNames"], ["EnumProcessModules", "EnumProcessModules", "SafeHandle hProcess, Span lphModule, out uint lpcbNeeded"], + ["AdjustTokenPrivileges", "AdjustTokenPrivileges", "SafeHandle TokenHandle, winmdroot.Foundation.BOOL DisableAllPrivileges, Span NewState, Span PreviousState, out uint ReturnLength"], + ["IMAPIStatus", "FlushQueues", "nuint ulUIParam, ReadOnlySpan lpTargetTransport, uint ulFlags"], ]; [Theory] From 184eb5101de96ef5839967df60b954f5905220cf Mon Sep 17 00:00:00 2001 From: Jevan Saks Date: Tue, 25 Nov 2025 14:16:24 -0800 Subject: [PATCH 2/2] Fix test failures --- test/Microsoft.Windows.CsWin32.Tests/COMTests.cs | 5 ++++- test/Microsoft.Windows.CsWin32.Tests/ExternMethodTests.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs b/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs index 9aa26fda..98e300fc 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/COMTests.cs @@ -404,8 +404,11 @@ public void ITypeNameBuilder_ToStringOverload(bool allowMarshaling) [Fact] public void ReferencesToStructWithFlexibleArrayAreAlwaysPointers() { + this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { FriendlyOverloads = DefaultTestGeneratorOptions.FriendlyOverloads with { IncludePointerOverloads = true } }); this.GenerateApi("IAMLine21Decoder"); - Assert.All(this.FindGeneratedMethod("SetOutputFormat"), m => Assert.IsType(m.ParameterList.Parameters[0].Type)); + + // At least one of the methods should take pointers. + Assert.Contains(this.FindGeneratedMethod("SetOutputFormat"), m => m.ParameterList.Parameters[0].Type is PointerTypeSyntax); // Assert that the 'unmanaged' declaration of the struct is the *only* declaration. Assert.Single(this.FindGeneratedType("BITMAPINFO")); diff --git a/test/Microsoft.Windows.CsWin32.Tests/ExternMethodTests.cs b/test/Microsoft.Windows.CsWin32.Tests/ExternMethodTests.cs index c76a80cd..fe38a01c 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/ExternMethodTests.cs +++ b/test/Microsoft.Windows.CsWin32.Tests/ExternMethodTests.cs @@ -78,8 +78,11 @@ public void DefaultEntryPointIsNotEmitted() [Fact] public void ReferencesToStructWithFlexibleArrayAreAlwaysPointers() { + this.generator = this.CreateGenerator(DefaultTestGeneratorOptions with { FriendlyOverloads = DefaultTestGeneratorOptions.FriendlyOverloads with { IncludePointerOverloads = true } }); this.GenerateApi("CreateDIBSection"); - Assert.All(this.FindGeneratedMethod("CreateDIBSection"), m => Assert.IsType(m.ParameterList.Parameters[1].Type)); + + // At least one of the methods should take pointers. + Assert.Contains(this.FindGeneratedMethod("CreateDIBSection"), m => m.ParameterList.Parameters[1].Type is PointerTypeSyntax); // Assert that the 'unmanaged' declaration of the struct is the *only* declaration. Assert.Single(this.FindGeneratedType("BITMAPINFO"));