From d4ab64e629533a40a7a9590f4d9f2c71f205476a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 2 Sep 2020 00:48:13 +0800 Subject: [PATCH] Add SafeBuffer Span methods (#40842) * Implement SafeBuffer.ReadSpan and WriteSpan. * Add reference source for SafeBuffer. * Add empty span test for SafeBuffer. * Add roundtrip tests for SafeBuffer. Co-authored-by: Jan Kotas --- .../System/Runtime/InteropServices/SafeBuffer.cs | 38 +++++---- .../Runtime/InteropServices/SafeBufferTests.cs | 94 ++++++++++++++++++++++ src/libraries/System.Runtime/ref/System.Runtime.cs | 4 + 3 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeBuffer.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeBuffer.cs index 80ec9c9..5a4ce91 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeBuffer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeBuffer.cs @@ -217,26 +217,30 @@ namespace System.Runtime.InteropServices if (array.Length - index < count) throw new ArgumentException(SR.Argument_InvalidOffLen); + ReadSpan(byteOffset, new Span(array, index, count)); + } + + [CLSCompliant(false)] + public void ReadSpan(ulong byteOffset, Span buffer) + where T : struct + { if (_numBytes == Uninitialized) throw NotInitialized(); uint sizeofT = SizeOf(); uint alignedSizeofT = AlignedSizeOf(); byte* ptr = (byte*)handle + byteOffset; - SpaceCheck(ptr, checked((nuint)(alignedSizeofT * count))); + SpaceCheck(ptr, checked((nuint)(alignedSizeofT * buffer.Length))); bool mustCallRelease = false; try { DangerousAddRef(ref mustCallRelease); - if (count > 0) + fixed (byte* pStructure = &Unsafe.As(ref MemoryMarshal.GetReference(buffer))) { - fixed (byte* pStructure = &Unsafe.As(ref array[index])) - { - for (int i = 0; i < count; i++) - Buffer.Memmove(pStructure + sizeofT * i, ptr + alignedSizeofT * i, sizeofT); - } + for (int i = 0; i < buffer.Length; i++) + Buffer.Memmove(pStructure + sizeofT * i, ptr + alignedSizeofT * i, sizeofT); } } finally @@ -293,28 +297,30 @@ namespace System.Runtime.InteropServices if (array.Length - index < count) throw new ArgumentException(SR.Argument_InvalidOffLen); + WriteSpan(byteOffset, new ReadOnlySpan(array, index, count)); + } + + [CLSCompliant(false)] + public void WriteSpan(ulong byteOffset, ReadOnlySpan data) + where T : struct + { if (_numBytes == Uninitialized) throw NotInitialized(); uint sizeofT = SizeOf(); uint alignedSizeofT = AlignedSizeOf(); byte* ptr = (byte*)handle + byteOffset; - SpaceCheck(ptr, checked((nuint)(alignedSizeofT * count))); + SpaceCheck(ptr, checked((nuint)(alignedSizeofT * data.Length))); bool mustCallRelease = false; try { DangerousAddRef(ref mustCallRelease); - if (count > 0) + fixed (byte* pStructure = &Unsafe.As(ref MemoryMarshal.GetReference(data))) { - { - fixed (byte* pStructure = &Unsafe.As(ref array[index])) - { - for (int i = 0; i < count; i++) - Buffer.Memmove(ptr + alignedSizeofT * i, pStructure + sizeofT * i, sizeofT); - } - } + for (int i = 0; i < data.Length; i++) + Buffer.Memmove(ptr + alignedSizeofT * i, pStructure + sizeofT * i, sizeofT); } } finally diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/SafeBufferTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/SafeBufferTests.cs index bacd62c..8a72ef8 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/SafeBufferTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/SafeBufferTests.cs @@ -77,6 +77,16 @@ namespace System.Runtime.InteropServices.Tests } [Fact] + public void ReadWriteSpan_EmptySpan_Passes() + { + var buffer = new SubBuffer(true); + buffer.Initialize(0); + + buffer.ReadSpan(0, Span.Empty); + buffer.WriteSpan(0, ReadOnlySpan.Empty); + } + + [Fact] public void ReadArray_NegativeIndex_ThrowsArgumentOutOfRangeException() { var wrapper = new SubBuffer(true); @@ -121,6 +131,68 @@ namespace System.Runtime.InteropServices.Tests Assert.Throws(() => wrapper.ByteLength); } + [Fact] + public void ReadWrite_RoundTrip() + { + using var buffer = new HGlobalBuffer(100); + + int intValue = 1234; + buffer.Write(0, intValue); + Assert.Equal(intValue, buffer.Read(0)); + + double doubleValue = 123.45; + buffer.Write(10, doubleValue); + Assert.Equal(doubleValue, buffer.Read(10)); + + TestStruct structValue = new TestStruct + { + I = 1234, + L = 987654321, + D = double.MaxValue + }; + buffer.Write(0, structValue); + Assert.Equal(structValue, buffer.Read(0)); + } + + [Fact] + public void ReadWriteSpanArray_RoundTrip() + { + using var buffer = new HGlobalBuffer(200); + + int[] intArray = new int[] { 11, 22, 33, 44 }; + TestArray(intArray); + TestSpan(intArray); + + TestStruct[] structArray = new TestStruct[] + { + new TestStruct { I = 11, L = 22, D = 33 }, + new TestStruct { I = 44, L = 55, D = 66 }, + new TestStruct { I = 77, L = 88, D = 99 }, + new TestStruct { I = 100, L = 200, D = 300 }, + }; + TestArray(structArray); + TestSpan(structArray); + + void TestArray(T[] data) + where T : struct + { + T[] destination = new T[data.Length]; + buffer.WriteArray(0, data, 0, data.Length); + buffer.ReadArray(0, destination, 0, data.Length); + Assert.Equal(data, destination); + } + + void TestSpan(ReadOnlySpan data) + where T : unmanaged + { + Span destination = stackalloc T[data.Length]; + buffer.WriteSpan(0, data); + buffer.ReadSpan(0, destination); + for (int i = 0; i < data.Length; i++) + Assert.Equal(data[i], destination[i]); + } + } + public class SubBuffer : SafeBuffer { public SubBuffer(bool ownsHandle) : base(ownsHandle) { } @@ -130,5 +202,27 @@ namespace System.Runtime.InteropServices.Tests throw new NotImplementedException(); } } + + public class HGlobalBuffer : SafeBuffer + { + public HGlobalBuffer(int length) : base(true) + { + SetHandle(Marshal.AllocHGlobal(length)); + Initialize((ulong)length); + } + + protected override bool ReleaseHandle() + { + Marshal.FreeHGlobal(handle); + return true; + } + } + + public struct TestStruct + { + public int I; + public long L; + public double D; + } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 4627ef9..cea6d87 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9738,11 +9738,15 @@ namespace System.Runtime.InteropServices [System.CLSCompliantAttribute(false)] public void ReadArray(ulong byteOffset, T[] array, int index, int count) where T : struct { } [System.CLSCompliantAttribute(false)] + public void ReadSpan(ulong byteOffset, System.Span buffer) where T : struct { } + [System.CLSCompliantAttribute(false)] public T Read(ulong byteOffset) where T : struct { throw null; } public void ReleasePointer() { } [System.CLSCompliantAttribute(false)] public void WriteArray(ulong byteOffset, T[] array, int index, int count) where T : struct { } [System.CLSCompliantAttribute(false)] + public void WriteSpan(ulong byteOffset, System.ReadOnlySpan data) where T : struct { } + [System.CLSCompliantAttribute(false)] public void Write(ulong byteOffset, T value) where T : struct { } } public abstract partial class SafeHandle : System.Runtime.ConstrainedExecution.CriticalFinalizerObject, System.IDisposable -- 2.7.4