From a2bee4d7f596b7b816e1d05ed2d5dcba1fef67cc Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:56:36 -0700 Subject: [PATCH 1/4] Create ManagedToUnmanagedOut ReadOnlySpan marshaller --- .../Marshalling/ReadOnlySpanMarshaller.cs | 32 +++++++++++++++++++ .../CollectionTests.cs | 24 ++++++++++++-- .../System.Runtime/ref/System.Runtime.cs | 10 ++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs index bab60b629e319e..8cf8f3159cbc0b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs @@ -20,6 +20,7 @@ namespace System.Runtime.InteropServices.Marshalling /// [CLSCompliant(false)] [CustomMarshaller(typeof(ReadOnlySpan<>), MarshalMode.ManagedToUnmanagedIn, typeof(ReadOnlySpanMarshaller<,>.ManagedToUnmanagedIn))] + [CustomMarshaller(typeof(ReadOnlySpan<>), MarshalMode.ManagedToUnmanagedOut, typeof(ReadOnlySpanMarshaller<,>.ManagedToUnmanagedOut))] [CustomMarshaller(typeof(ReadOnlySpan<>), MarshalMode.UnmanagedToManagedOut, typeof(ReadOnlySpanMarshaller<,>.UnmanagedToManagedOut))] [ContiguousCollectionMarshaller] public static unsafe class ReadOnlySpanMarshaller @@ -166,5 +167,36 @@ public static ref T GetPinnableReference(ReadOnlySpan managed) return ref MemoryMarshal.GetReference(managed); } } + + public struct ManagedToUnmanagedOut + { + private TUnmanagedElement* _unmanagedArray; + private T[]? _managedValues; + + public void FromUnmanaged(TUnmanagedElement* unmanaged) + { + _unmanagedArray = unmanaged; + } + + public ReadOnlySpan ToManaged() + { + return new ReadOnlySpan(_managedValues!); + } + + public ReadOnlySpan GetUnmanagedValuesSource(int numElements) + { + return new ReadOnlySpan ( _unmanagedArray, numElements); + } + + public Span GetManagedValuesDestination(int numElements) + { + return _managedValues = new T[numElements]; + } + + public void Free() + { + NativeMemory.Free(_unmanagedArray); + } + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs index 51214883bd4b3e..854830692c16a2 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Text; @@ -115,13 +116,24 @@ public partial class Stateful [return: MarshalUsing(typeof(ListMarshallerStateful<,>), CountElementName = "numValues")] public static partial List CreateRange(int start, int end, out int numValues); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "create_range_array")] + [return: MarshalUsing(CountElementName = nameof(numValues))] + public static partial ReadOnlySpan CreateRangeReadOnlySpan(int start, int end, out int numValues); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "create_range_array_out")] public static partial void CreateRange_Out(int start, int end, out int numValues, [MarshalUsing(typeof(ListMarshallerStateful<,>), CountElementName = "numValues")] out List res); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "create_range_array_out")] + public static partial void CreateRange_Out_ReadOnlySpan(int start, int end, out int numValues, [MarshalUsing(CountElementName = nameof(numValues))] out ReadOnlySpan res); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_long_bytes")] [return: MarshalUsing(typeof(ListMarshallerStateful<,>), ConstantElementCount = sizeof(long))] public static partial List GetLongBytes(long l); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "get_long_bytes")] + [return: MarshalUsing(ConstantElementCount = sizeof(long))] + public static partial ReadOnlySpan GetLongBytesReadOnlySpan(long l); + [LibraryImport(NativeExportsNE_Binary, EntryPoint = "and_bool_struct_array")] [return: MarshalAs(UnmanagedType.U1)] public static partial bool AndAllMembers([MarshalUsing(typeof(ListMarshallerStateful<,>))] List pArray, int length); @@ -171,7 +183,7 @@ public List ToManagedFinally() public Span GetManagedValuesDestination(int length) => default; public ReadOnlySpan GetUnmanagedValuesSource(int length) => default; public void FromUnmanaged(byte* value) { } - public void Free() {} + public void Free() { } } } } @@ -235,10 +247,11 @@ public void BlittableElementCollection_OutReturn() { int start = 5; int end = 20; - IEnumerable expected = Enumerable.Range(start, end - start); + int[] expected = Enumerable.Range(start, end - start).ToArray(); Assert.Equal(expected, NativeExportsNE.Collections.Stateless.CreateRange(start, end, out _)); Assert.Equal(expected, NativeExportsNE.Collections.Stateful.CreateRange(start, end, out _)); + Assert.Equal(expected, NativeExportsNE.Collections.Stateful.CreateRangeReadOnlySpan(start, end, out _)); { List res; @@ -249,6 +262,9 @@ public void BlittableElementCollection_OutReturn() List res; NativeExportsNE.Collections.Stateful.CreateRange_Out(start, end, out _, out res); Assert.Equal(expected, res); + ReadOnlySpan res2; + NativeExportsNE.Collections.Stateful.CreateRange_Out_ReadOnlySpan(start, end, out _, out res2); + Assert.Equal(expected, res2); } } @@ -267,6 +283,9 @@ public void BlittableElementCollection_OutReturn_Null() List res; NativeExportsNE.Collections.Stateful.CreateRange_Out(1, 0, out _, out res); Assert.Null(res); + ReadOnlySpan res2; + NativeExportsNE.Collections.Stateful.CreateRange_Out_ReadOnlySpan(1, 0, out _, out res2); + Assert.True(res2.IsEmpty); } } @@ -289,6 +308,7 @@ public void ConstantSizeCollection() Assert.Equal(longVal, MemoryMarshal.Read(CollectionsMarshal.AsSpan(NativeExportsNE.Collections.Stateless.GetLongBytes(longVal)))); Assert.Equal(longVal, MemoryMarshal.Read(CollectionsMarshal.AsSpan(NativeExportsNE.Collections.Stateful.GetLongBytes(longVal)))); + Assert.Equal(longVal, MemoryMarshal.Read(NativeExportsNE.Collections.Stateful.GetLongBytesReadOnlySpan(longVal))); } [Theory] diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e8ef65ea314449..b04cc91b6a9fc2 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13743,6 +13743,7 @@ public NativeMarshallingAttribute(System.Type nativeType) { } [System.CLSCompliantAttribute(false)] [System.Runtime.InteropServices.Marshalling.ContiguousCollectionMarshallerAttribute] [System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.ReadOnlySpan<>), System.Runtime.InteropServices.Marshalling.MarshalMode.ManagedToUnmanagedIn, typeof(System.Runtime.InteropServices.Marshalling.ReadOnlySpanMarshaller<,>.ManagedToUnmanagedIn))] + [System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.ReadOnlySpan<>), System.Runtime.InteropServices.Marshalling.MarshalMode.ManagedToUnmanagedOut, typeof(System.Runtime.InteropServices.Marshalling.ReadOnlySpanMarshaller<,>.ManagedToUnmanagedOut))] [System.Runtime.InteropServices.Marshalling.CustomMarshallerAttribute(typeof(System.ReadOnlySpan<>), System.Runtime.InteropServices.Marshalling.MarshalMode.UnmanagedToManagedOut, typeof(System.Runtime.InteropServices.Marshalling.ReadOnlySpanMarshaller<,>.UnmanagedToManagedOut))] public static unsafe partial class ReadOnlySpanMarshaller where TUnmanagedElement : unmanaged { @@ -13759,6 +13760,15 @@ public void FromManaged(System.ReadOnlySpan managed, System.Span GetUnmanagedValuesDestination() { throw null; } public unsafe TUnmanagedElement* ToUnmanaged() { throw null; } } + public partial struct ManagedToUnmanagedOut + { + private object _dummy; + public void FromUnmanaged(TUnmanagedElement* unmanaged) { throw null; } + public ReadOnlySpan ToManaged() { throw null; } + public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { throw null; } + public Span GetManagedValuesDestination(int numElements) { throw null; } + public void Free() { throw null; } + } public static partial class UnmanagedToManagedOut { public unsafe static TUnmanagedElement* AllocateContainerForUnmanagedElements(System.ReadOnlySpan managed, out int numElements) { throw null; } From 35d1a625e8950d711ba3105361afc2b45607185b Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:08:35 -0700 Subject: [PATCH 2/4] Add documentation comments to new Marshaller --- .../Marshalling/ReadOnlySpanMarshaller.cs | 26 +++++++++++++++++-- .../System.Runtime/ref/System.Runtime.cs | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs index 8cf8f3159cbc0b..4d05348e4c0890 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs @@ -88,7 +88,7 @@ public ref struct ManagedToUnmanagedIn private Span _span; /// - /// Initializes the marshaller. + /// Initializes the marshaller. /// /// The span to be marshalled. /// The buffer that may be used for marshalling. @@ -168,31 +168,53 @@ public static ref T GetPinnableReference(ReadOnlySpan managed) } } + /// + /// Supports marshalling from unmanaged to managed in a call from managed code to unmanaged code. For example, return values and `out` parameters in P/Invoke methods. + /// public struct ManagedToUnmanagedOut { private TUnmanagedElement* _unmanagedArray; private T[]? _managedValues; + /// + /// Initializes the marshaller. + /// + /// A pointer to the array to be unmarshalled from native to managed. public void FromUnmanaged(TUnmanagedElement* unmanaged) { _unmanagedArray = unmanaged; } + /// + /// Returns the managed value representing the native array. + /// public ReadOnlySpan ToManaged() { return new ReadOnlySpan(_managedValues!); } + /// + /// Returns a span that points to the memory where the unmanaged elements of the array are stored. + /// + /// A span over unmanaged values of the array. public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { return new ReadOnlySpan ( _unmanagedArray, numElements); } + /// + /// Returns a span that points to the memory where the managed elements of the array should be stored. + /// + /// A span where managed values of the array should be stored. public Span GetManagedValuesDestination(int numElements) { - return _managedValues = new T[numElements]; + _managedValues = new T[numElements]; + return _managedValues; } + /// + /// Frees resources. + /// public void Free() { NativeMemory.Free(_unmanagedArray); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index b04cc91b6a9fc2..4591e9674d4b20 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13763,6 +13763,7 @@ public void FromManaged(System.ReadOnlySpan managed, System.Span ToManaged() { throw null; } public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { throw null; } From b4b78366935a83646b21591559fc7b212f0020db Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:46:05 -0700 Subject: [PATCH 3/4] Use correct 'free' for native memory cleanup --- .../InteropServices/Marshalling/ReadOnlySpanMarshaller.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs index 4d05348e4c0890..93e6db64729e81 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ReadOnlySpanMarshaller.cs @@ -199,7 +199,7 @@ public ReadOnlySpan ToManaged() /// A span over unmanaged values of the array. public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { - return new ReadOnlySpan ( _unmanagedArray, numElements); + return new ReadOnlySpan(_unmanagedArray, numElements); } /// @@ -217,7 +217,7 @@ public Span GetManagedValuesDestination(int numElements) /// public void Free() { - NativeMemory.Free(_unmanagedArray); + Marshal.FreeCoTaskMem((IntPtr)_unmanagedArray); } } } From 3a2c2bfa07f0e8d08c444fe2b2fb358ed322dce6 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:52:18 -0700 Subject: [PATCH 4/4] PR feedback: fully qualify Span, use new scope for new test --- .../CollectionTests.cs | 22 ++++++++++--------- .../System.Runtime/ref/System.Runtime.cs | 6 ++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs index 854830692c16a2..5892dcce98b0f4 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionTests.cs @@ -253,18 +253,19 @@ public void BlittableElementCollection_OutReturn() Assert.Equal(expected, NativeExportsNE.Collections.Stateful.CreateRange(start, end, out _)); Assert.Equal(expected, NativeExportsNE.Collections.Stateful.CreateRangeReadOnlySpan(start, end, out _)); + List res; + ReadOnlySpan resSpan; { - List res; NativeExportsNE.Collections.Stateless.CreateRange_Out(start, end, out _, out res); Assert.Equal(expected, res); } { - List res; NativeExportsNE.Collections.Stateful.CreateRange_Out(start, end, out _, out res); Assert.Equal(expected, res); - ReadOnlySpan res2; - NativeExportsNE.Collections.Stateful.CreateRange_Out_ReadOnlySpan(start, end, out _, out res2); - Assert.Equal(expected, res2); + } + { + NativeExportsNE.Collections.Stateful.CreateRange_Out_ReadOnlySpan(start, end, out _, out resSpan); + Assert.Equal(expected, resSpan); } } @@ -274,18 +275,19 @@ public void BlittableElementCollection_OutReturn_Null() Assert.Null(NativeExportsNE.Collections.Stateless.CreateRange(1, 0, out _)); Assert.Null(NativeExportsNE.Collections.Stateful.CreateRange(1, 0, out _)); + List res; + ReadOnlySpan resSpan; { - List res; NativeExportsNE.Collections.Stateless.CreateRange_Out(1, 0, out _, out res); Assert.Null(res); } { - List res; NativeExportsNE.Collections.Stateful.CreateRange_Out(1, 0, out _, out res); Assert.Null(res); - ReadOnlySpan res2; - NativeExportsNE.Collections.Stateful.CreateRange_Out_ReadOnlySpan(1, 0, out _, out res2); - Assert.True(res2.IsEmpty); + } + { + NativeExportsNE.Collections.Stateful.CreateRange_Out_ReadOnlySpan(1, 0, out _, out resSpan); + Assert.True(resSpan.IsEmpty); } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 041ee9736c33f5..ed4f457879243d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14152,9 +14152,9 @@ public partial struct ManagedToUnmanagedOut private object _dummy; private int _dummyPrimitive; public void FromUnmanaged(TUnmanagedElement* unmanaged) { throw null; } - public ReadOnlySpan ToManaged() { throw null; } - public ReadOnlySpan GetUnmanagedValuesSource(int numElements) { throw null; } - public Span GetManagedValuesDestination(int numElements) { throw null; } + public System.ReadOnlySpan ToManaged() { throw null; } + public System.ReadOnlySpan GetUnmanagedValuesSource(int numElements) { throw null; } + public System.Span GetManagedValuesDestination(int numElements) { throw null; } public void Free() { throw null; } } public static partial class UnmanagedToManagedOut