From 4aa8b2464be61ae1fccc8b9080a34c1e7f20b011 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 14 Jul 2025 01:03:53 -0700 Subject: [PATCH 1/5] Fix non bittable array marshaling --- .../FunctionalTests/Collections/Program.cs | 71 +++++++++++++++++++ src/WinRT.Runtime/Marshalers.cs | 24 +++---- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/Tests/FunctionalTests/Collections/Program.cs b/src/Tests/FunctionalTests/Collections/Program.cs index 6ff5f36699..8a5b755479 100644 --- a/src/Tests/FunctionalTests/Collections/Program.cs +++ b/src/Tests/FunctionalTests/Collections/Program.cs @@ -6,6 +6,7 @@ using System.Threading; using test_component_derived.Nested; using TestComponentCSharp; +using Windows.Foundation; var instance = new Class(); @@ -40,6 +41,76 @@ return 101; } +string[] stringArr = new string[] { "apples", "oranges", "pears" }; +string[] stringArr2 = new string[stringArr.Length]; +string[] outStringArr; +string[] retStringArr = instance2.Array12(stringArr, stringArr2, out outStringArr); +if (!AllEqual(stringArr, stringArr2, outStringArr, retStringArr)) +{ + return 101; +} + +TestComponent.Blittable[] blittableArr = new TestComponent.Blittable[] { + new TestComponent.Blittable(1, 2, 3, 4, -5, -6, -7, 8.0f, 9.0, typeof(TestComponent.ITests).GUID), + new TestComponent.Blittable(10, 20, 30, 40, -50, -60, -70, 80.0f, 90.0, typeof(IStringable).GUID) + }; +TestComponent.Blittable[] blittableArr2 = new TestComponent.Blittable[blittableArr.Length]; +TestComponent.Blittable[] outBlittableArr; +TestComponent.Blittable[] retBlittableArr = instance2.Array13(blittableArr, blittableArr2, out outBlittableArr); +if (!AllEqual(blittableArr, blittableArr2, outBlittableArr, retBlittableArr)) +{ + return 101; +} + +#if NET9_0_OR_GREATER + +TestComponent.NonBlittable[] nonBlittableArr = new TestComponent.NonBlittable[] { + new TestComponent.NonBlittable(false, 'X', "First", (long?)PropertyValue.CreateInt64(123)), + new TestComponent.NonBlittable(true, 'Y', "Second", (long?)PropertyValue.CreateInt64(456)), + new TestComponent.NonBlittable(false, 'Z', "Third", (long?)PropertyValue.CreateInt64(789)) + }; +TestComponent.NonBlittable[] nonBlittableArr2 = new TestComponent.NonBlittable[nonBlittableArr.Length]; +TestComponent.NonBlittable[] outNonBlittableArr; +TestComponent.NonBlittable[] retNonBlittableArr = instance2.Array14(nonBlittableArr, nonBlittableArr2, out outNonBlittableArr); +if (!AllEqual(nonBlittableArr, nonBlittableArr2, outNonBlittableArr, retNonBlittableArr)) +{ + return 101; +} + +TestComponent.Nested[] nestedArr = new TestComponent.Nested[]{ + new TestComponent.Nested( + new TestComponent.Blittable(1, 2, 3, 4, -5, -6, -7, 8.0f, 9.0, typeof(TestComponent.ITests).GUID), + new TestComponent.NonBlittable(false, 'X', "First", (long?)PropertyValue.CreateInt64(123))), + new TestComponent.Nested( + new TestComponent.Blittable(10, 20, 30, 40, -50, -60, -70, 80.0f, 90.0, typeof(IStringable).GUID), + new TestComponent.NonBlittable(true, 'Y', "Second", (long?)PropertyValue.CreateInt64(456))), + new TestComponent.Nested( + new TestComponent.Blittable(1, 2, 3, 4, -5, -6, -7, 8.0f, 9.0, typeof(WinRT.IInspectable).GUID), + new TestComponent.NonBlittable(false, 'Z', "Third", (long?)PropertyValue.CreateInt64(789))) + }; +TestComponent.Nested[] nestedArr2 = new TestComponent.Nested[nestedArr.Length]; +TestComponent.Nested[] outNestedArr; +TestComponent.Nested[] retNestedArr = instance2.Array15(nestedArr, nestedArr2, out outNestedArr); +if (!AllEqual(nestedArr, nestedArr2, outNestedArr, retNestedArr)) +{ + return 101; +} + +#endif + +IStringable[] stringableArr = new IStringable[] { + Windows.Data.Json.JsonValue.CreateNumberValue(3), + Windows.Data.Json.JsonValue.CreateNumberValue(4), + Windows.Data.Json.JsonValue.CreateNumberValue(5.0) + }; +IStringable[] stringableArr2 = new IStringable[stringableArr.Length]; +IStringable[] outStringableArr; +IStringable[] retStringableArr = instance2.Array16(stringableArr, stringableArr2, out outStringableArr); +if (!AllEqual(stringableArr, stringableArr2, outStringableArr, retStringableArr)) +{ + return 101; +} + var hierarchyDAsObjectList = HierarchyC.CreateDerivedHierarchyDAsObjectList(); foreach (var hierarchyDAsObject in hierarchyDAsObjectList) { diff --git a/src/WinRT.Runtime/Marshalers.cs b/src/WinRT.Runtime/Marshalers.cs index 4c8064d0b7..0a144477d4 100644 --- a/src/WinRT.Runtime/Marshalers.cs +++ b/src/WinRT.Runtime/Marshalers.cs @@ -1088,7 +1088,7 @@ public void Dispose() { foreach (var marshaler in _marshalers) { - Marshaler.DisposeMarshaler(marshaler); + MarshalNonBlittable.DisposeMarshaler(marshaler); } } if (_array != IntPtr.Zero) @@ -1103,7 +1103,7 @@ public void Dispose() public static new unsafe MarshalerArray CreateMarshalerArray(T[] array) { -#if NET +#if NET && !NET9_0_OR_GREATER if (!RuntimeFeature.IsDynamicCodeCompiled) { throw new NotSupportedException($"Cannot handle array marshalling for non blittable type '{typeof(T)}'."); @@ -1131,8 +1131,8 @@ public void Dispose() var element = (byte*)m._array.ToPointer(); for (int i = 0; i < length; i++) { - m._marshalers[i] = Marshaler.CreateMarshaler(array[i]); - Marshaler.CopyAbi(m._marshalers[i], (IntPtr)element); + m._marshalers[i] = MarshalNonBlittable.CreateMarshaler(array[i]); + MarshalNonBlittable.CopyAbi(m._marshalers[i], (IntPtr)element); element += abi_element_size; } #pragma warning restore IL3050 @@ -1156,7 +1156,7 @@ public void Dispose() public static new unsafe T[] FromAbiArray(object box) { -#if NET +#if NET && !NET9_0_OR_GREATER if (!RuntimeFeature.IsDynamicCodeCompiled) { throw new NotSupportedException($"Cannot handle array marshalling for non blittable type '{typeof(T)}'."); @@ -1188,7 +1188,7 @@ public void Dispose() #else Marshal.PtrToStructure((IntPtr)data, AbiType); #endif - array[i] = Marshaler.FromAbi(abi_element); + array[i] = MarshalNonBlittable.FromAbi(abi_element); data += abi_element_size; } #pragma warning restore IL3050 @@ -1197,7 +1197,7 @@ public void Dispose() public static unsafe void CopyAbiArray(T[] array, object box) { -#if NET +#if NET && !NET9_0_OR_GREATER if (!RuntimeFeature.IsDynamicCodeCompiled) { throw new NotSupportedException($"Cannot handle array marshalling for non blittable type '{typeof(T)}'."); @@ -1224,7 +1224,7 @@ public static unsafe void CopyAbiArray(T[] array, object box) #else Marshal.PtrToStructure((IntPtr)data, AbiType); #endif - array[i] = Marshaler.FromAbi(abi_element); + array[i] = MarshalNonBlittable.FromAbi(abi_element); data += abi_element_size; } #pragma warning restore IL3050 @@ -1232,7 +1232,7 @@ public static unsafe void CopyAbiArray(T[] array, object box) public static new unsafe (int length, IntPtr data) FromManagedArray(T[] array) { -#if NET +#if NET && !NET9_0_OR_GREATER if (!RuntimeFeature.IsDynamicCodeCompiled) { throw new NotSupportedException($"Cannot handle array marshalling for non blittable type '{typeof(T)}'."); @@ -1260,7 +1260,7 @@ public static unsafe void CopyAbiArray(T[] array, object box) var bytes = (byte*)data.ToPointer(); for (i = 0; i < length; i++) { - Marshaler.CopyManaged(array[i], (IntPtr)bytes); + MarshalNonBlittable.CopyManaged(array[i], (IntPtr)bytes); bytes += abi_element_size; } #pragma warning restore IL3050 @@ -1278,7 +1278,7 @@ public static unsafe void CopyAbiArray(T[] array, object box) public static unsafe void CopyManagedArray(T[] array, IntPtr data) { -#if NET +#if NET && !NET9_0_OR_GREATER if (!RuntimeFeature.IsDynamicCodeCompiled) { throw new NotSupportedException($"Cannot handle array marshalling for non blittable type '{typeof(T)}'."); @@ -1304,7 +1304,7 @@ public static unsafe void CopyManagedArray(T[] array, IntPtr data) var bytes = (byte*)data.ToPointer(); for (i = 0; i < length; i++) { - Marshaler.CopyManaged(array[i], (IntPtr)bytes); + MarshalNonBlittable.CopyManaged(array[i], (IntPtr)bytes); bytes += abi_element_size; } #pragma warning restore IL3050 From 2b2d9a2f18ffa352b8eacfed9f909dbe0005d9aa Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 14 Jul 2025 01:13:46 -0700 Subject: [PATCH 2/5] Move AOT tests to .NET 9 --- .../CsWinRT-FunctionalTest-Steps.yml | 12 ++++++------ src/Directory.Build.props | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build/AzurePipelineTemplates/CsWinRT-FunctionalTest-Steps.yml b/build/AzurePipelineTemplates/CsWinRT-FunctionalTest-Steps.yml index 5ae79362d0..b742f26954 100644 --- a/build/AzurePipelineTemplates/CsWinRT-FunctionalTest-Steps.yml +++ b/build/AzurePipelineTemplates/CsWinRT-FunctionalTest-Steps.yml @@ -62,20 +62,20 @@ steps: TargetFolder: $(StagingFolder)\FunctionalTests\${{ functionalTest }}\ - task: MSBuild@1 - displayName: Publish ${{ functionalTest }} for AOT (net8.0) + displayName: Publish ${{ functionalTest }} for AOT (net9.0) condition: and(succeeded(), and(eq(variables['BuildConfiguration'], 'release'), eq(variables['BuildPlatform'], 'x64'))) inputs: solution: $(Build.SourcesDirectory)\src\Tests\FunctionalTests\${{ functionalTest }}\${{ functionalTest }}.csproj - msbuildArguments: /t:publish /p:CIBuildReason=CI,RuntimeIdentifier=win-$(BuildPlatform),TargetFramework=net8.0,solutiondir=$(Build.SourcesDirectory)\src\,VersionNumber=$(VersionNumber),VersionString=$(Build.BuildNumber),AssemblyVersionNumber=$(WinRT.Runtime.AssemblyVersion),GenerateTestProjection=true,AllowedReferenceRelatedFileExtensions=".xml;.pri;.dll.config;.exe.config" + msbuildArguments: /t:publish /p:CIBuildReason=CI,RuntimeIdentifier=win-$(BuildPlatform),TargetFramework=net9.0,solutiondir=$(Build.SourcesDirectory)\src\,VersionNumber=$(VersionNumber),VersionString=$(Build.BuildNumber),AssemblyVersionNumber=$(WinRT.Runtime.AssemblyVersion),GenerateTestProjection=true,AllowedReferenceRelatedFileExtensions=".xml;.pri;.dll.config;.exe.config" platform: $(BuildPlatform) configuration: $(BuildConfiguration) - task: CmdLine@2 - displayName: Run ${{ functionalTest }} for AOT (net8.0) + displayName: Run ${{ functionalTest }} for AOT (net9.0) condition: and(succeeded(), and(eq(variables['BuildConfiguration'], 'release'), eq(variables['BuildPlatform'], 'x64'))) continueOnError: True inputs: - workingDirectory: $(Build.SourcesDirectory)\src\Tests\FunctionalTests\${{ functionalTest }}\bin\$(BuildConfiguration)\net8.0\win-$(BuildPlatform)\publish + workingDirectory: $(Build.SourcesDirectory)\src\Tests\FunctionalTests\${{ functionalTest }}\bin\$(BuildConfiguration)\net9.0\win-$(BuildPlatform)\publish script: | dir echo Running ${{ functionalTest }}.exe @@ -86,8 +86,8 @@ steps: exit /b 0 - task: CopyFiles@2 - displayName: Copy ${{ functionalTest }} for AOT (net8.0) + displayName: Copy ${{ functionalTest }} for AOT (net9.0) condition: and(eq(variables['_PublishFunctionalTests'], 'true'), and(eq(variables['BuildConfiguration'], 'release'), eq(variables['BuildPlatform'], 'x64'))) inputs: - SourceFolder: $(Build.SourcesDirectory)\src\Tests\FunctionalTests\${{ functionalTest }}\bin\$(BuildConfiguration)\net8.0\win-$(BuildPlatform)\publish + SourceFolder: $(Build.SourcesDirectory)\src\Tests\FunctionalTests\${{ functionalTest }}\bin\$(BuildConfiguration)\net9.0\win-$(BuildPlatform)\publish TargetFolder: $(StagingFolder)\FunctionalTests\${{ functionalTest }}\ diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 59c8ce0679..6becbb3232 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -35,7 +35,7 @@ net8.0;net9.0 net8.0-windows10.0.19041.0 net8.0-windows10.0.19041.0 - net8.0 + net8.0;net9.0 false high From 441de4892353bf1d306f2e00583dd9b7b8118306 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 14 Jul 2025 19:05:49 -0700 Subject: [PATCH 3/5] Fix enums --- .../FunctionalTests/Collections/Program.cs | 8 ++++++ src/WinRT.Runtime/Marshalers.cs | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/Tests/FunctionalTests/Collections/Program.cs b/src/Tests/FunctionalTests/Collections/Program.cs index 8a5b755479..b710477e4b 100644 --- a/src/Tests/FunctionalTests/Collections/Program.cs +++ b/src/Tests/FunctionalTests/Collections/Program.cs @@ -96,6 +96,14 @@ return 101; } +EnumValue[] enumArr = new EnumValue[] { EnumValue.One, EnumValue.Two }; +instance.EnumsProperty = enumArr; +EnumValue[] retEnumArr = instance.EnumsProperty; +if (!AllEqual(enumArr, retEnumArr)) +{ + return 101; +} + #endif IStringable[] stringableArr = new IStringable[] { diff --git a/src/WinRT.Runtime/Marshalers.cs b/src/WinRT.Runtime/Marshalers.cs index 0a144477d4..492d178959 100644 --- a/src/WinRT.Runtime/Marshalers.cs +++ b/src/WinRT.Runtime/Marshalers.cs @@ -685,6 +685,32 @@ static MarshalGeneric() DisposeMarshaler = ABI.System.NonBlittableMarshallingStubs.NoOpFunc; DisposeAbi = ABI.System.NonBlittableMarshallingStubs.NoOpFunc; } + else if (typeof(T).IsEnum) + { + Func ReturnTypedParameterFunc = (T value) => value; + AbiType = typeof(T); + CreateMarshaler = ReturnTypedParameterFunc; + CreateMarshaler2 = CreateMarshaler; + GetAbi = Marshaler.ReturnParameterFunc; + FromAbi = (object value) => (T)value; + FromManaged = ReturnTypedParameterFunc; + DisposeMarshaler = ABI.System.NonBlittableMarshallingStubs.NoOpFunc; + DisposeAbi = ABI.System.NonBlittableMarshallingStubs.NoOpFunc; + if (typeof(T).IsEnum) + { + // For marshaling non-blittable enum arrays via MarshalNonBlittable + if (typeof(T).GetEnumUnderlyingType() == typeof(int)) + { + CopyAbi = Marshaler.CopyIntEnumFunc; + CopyManaged = Marshaler.CopyIntEnumDirectFunc.WithTypedT1(); + } + else + { + CopyAbi = Marshaler.CopyUIntEnumFunc; + CopyManaged = Marshaler.CopyUIntEnumDirectFunc.WithTypedT1(); + } + } + } else if (typeof(T).IsValueType) { // Value types can have custom marshaller types and use value types in places where we can't construct From 8ee083edbf09a9271f30285fff36bb9baeeb49e9 Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Mon, 14 Jul 2025 23:01:21 -0700 Subject: [PATCH 4/5] Fix for .NET 9 --- .../JsonValueFunctionCalls.csproj | 2 +- .../JsonValueFunctionCalls/Program.cs | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Tests/FunctionalTests/JsonValueFunctionCalls/JsonValueFunctionCalls.csproj b/src/Tests/FunctionalTests/JsonValueFunctionCalls/JsonValueFunctionCalls.csproj index f65cc8b91b..df41ced1b3 100644 --- a/src/Tests/FunctionalTests/JsonValueFunctionCalls/JsonValueFunctionCalls.csproj +++ b/src/Tests/FunctionalTests/JsonValueFunctionCalls/JsonValueFunctionCalls.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs b/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs index dbe1054435..d8d2952788 100644 --- a/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs +++ b/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs @@ -1,6 +1,7 @@ using System; using Windows.Foundation; + // Static function calls and create RCW for existing object. IStringable[] a = new IStringable[] { Windows.Data.Json.JsonValue.CreateNumberValue(3), @@ -19,10 +20,16 @@ // Class function call result += (int)(a[1] as Windows.Data.Json.JsonValue).GetNumber(); -var enumVal = TestComponentCSharp.Class.BoxedEnum; -if (enumVal is TestComponentCSharp.EnumValue val && val == TestComponentCSharp.EnumValue.Two) -{ - result += 1; -} +CheckBoxedEnum(); -return result == 17 ? 100 : 101; \ No newline at end of file +return result == 17 ? 100 : 101; + +[WinRT.DynamicWindowsRuntimeCast(typeof(TestComponentCSharp.EnumValue))] +void CheckBoxedEnum() +{ + var enumVal = TestComponentCSharp.Class.BoxedEnum; + if (enumVal is TestComponentCSharp.EnumValue val && val == TestComponentCSharp.EnumValue.Two) + { + result += 1; + } +} \ No newline at end of file From 8d51e4b00dd690fdf011f821a270feeeccede41c Mon Sep 17 00:00:00 2001 From: Manodasan Wignarajah Date: Tue, 15 Jul 2025 18:33:27 -0700 Subject: [PATCH 5/5] PR feedback --- src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs | 1 - src/WinRT.Runtime/Marshalers.cs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs b/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs index d8d2952788..2d47eb4aa3 100644 --- a/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs +++ b/src/Tests/FunctionalTests/JsonValueFunctionCalls/Program.cs @@ -1,7 +1,6 @@ using System; using Windows.Foundation; - // Static function calls and create RCW for existing object. IStringable[] a = new IStringable[] { Windows.Data.Json.JsonValue.CreateNumberValue(3), diff --git a/src/WinRT.Runtime/Marshalers.cs b/src/WinRT.Runtime/Marshalers.cs index 492d178959..ec6f3f1692 100644 --- a/src/WinRT.Runtime/Marshalers.cs +++ b/src/WinRT.Runtime/Marshalers.cs @@ -1114,6 +1114,9 @@ public void Dispose() { foreach (var marshaler in _marshalers) { + // We make use of MarshalNonBlittable for array marshaling when T is non-blittable or when it is an enum. + // Both scenarios are handled by MarshalNonBlittable for marshaling T itself, so we just directly use that + // here and below without needing to go through Marshaler. MarshalNonBlittable.DisposeMarshaler(marshaler); } }