diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs index 2d36bd883..d2eb19e00 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs @@ -59,58 +59,91 @@ public static IBuffer AsBuffer(this byte[] source, int offset, int length, int c #endregion (Byte []).AsBuffer extensions -#region (Byte []).CopyTo extensions for copying to an (IBuffer) - - /// - /// Copies the contents of source to destination starting at offset 0. - /// This method does NOT update destination.Length. - /// - /// Array to copy data from. - /// The buffer to copy to. - public static void CopyTo(this byte[] source, IBuffer destination) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - - CopyTo(source, 0, destination, 0, source.Length); - } - - - /// - /// Copies count bytes from source starting at offset sourceIndex - /// to destination starting at destinationIndex. - /// This method does NOT update destination.Length. - /// - /// Array to copy data from. - /// Position in the array from where to start copying. - /// The buffer to copy to. - /// Position in the buffer to where to start copying. - /// The number of bytes to copy. - public static void CopyTo(this byte[] source, int sourceIndex, IBuffer destination, uint destinationIndex, int count) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (sourceIndex < 0) throw new ArgumentOutOfRangeException(nameof(sourceIndex)); - if (source.Length < sourceIndex) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_IndexOutOfArrayBounds, nameof(sourceIndex)); - if (source.Length - sourceIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (destination.Capacity < destinationIndex) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_BufferIndexExceedsCapacity); - if (destination.Capacity - destinationIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInTargetBuffer); - if (count == 0) return; - - // If destination is backed by a managed array, use the array instead of the pointer as it does not require pinning: - byte[] destDataArr; - int destDataOffs; - if (destination.TryGetUnderlyingData(out destDataArr, out destDataOffs)) - { - global::System.Buffer.BlockCopy(source, sourceIndex, destDataArr, (int)(destDataOffs + destinationIndex), count); - return; - } - - IntPtr destPtr = destination.GetPointerAtOffset(destinationIndex); - Marshal.Copy(source, sourceIndex, destPtr, count); - } - +#region (Span).CopyTo extensions for copying to an (IBuffer) + + /// + /// Copies the contents of source to destination starting at offset 0. + /// This method does NOT update destination.Length. + /// + /// Span to copy data from. + /// The buffer to copy to. + public static void CopyTo(this Span source, IBuffer destination) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + CopyTo(source, destination, 0); + } + + + /// + /// Copies count bytes from source starting at offset sourceIndex + /// to destination starting at destinationIndex. + /// This method does NOT update destination.Length. + /// + /// Span to copy data from. + /// Position in the span from where to start copying. + /// The buffer to copy to. + /// Position in the buffer to where to start copying. + /// The number of bytes to copy. + public static void CopyTo(this Span source, IBuffer destination, uint destinationIndex) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Capacity < destinationIndex) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_BufferIndexExceedsCapacity); + if (destination.Capacity - destinationIndex < source.Length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInTargetBuffer); + if (source.Length == 0) return; + + Debug.Assert(destinationIndex <= int.MaxValue); + + // If destination is backed by a managed memory, use the memory instead of the pointer as it does not require pinning: + Span destSpan = destination.TryGetUnderlyingData(out byte[] destDataArr, out int destOffset) ? destDataArr.AsSpan(destOffset + (int)destinationIndex) : destination.GetSpanForCapacityUnsafe(destinationIndex); + source.CopyTo(destSpan); + + // Ensure destination stays alive for the copy operation + GC.KeepAlive(destination); + + // Update Length last to make sure the data is valid + if (destinationIndex + source.Length > destination.Length) + { + destination.Length = destinationIndex + (uint)source.Length; + } + } + +#endregion (Span).CopyTo extensions for copying to an (IBuffer) + +#region (Byte []).CopyTo extensions for copying to an (IBuffer) + + /// + /// Copies the contents of source to destination starting at offset 0. + /// This method does NOT update destination.Length. + /// + /// Array to copy data from. + /// The buffer to copy to. + public static void CopyTo(this byte[] source, IBuffer destination) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + CopyTo(source.AsSpan(), destination, 0); + } + + + /// + /// Copies count bytes from source starting at offset sourceIndex + /// to destination starting at destinationIndex. + /// This method does NOT update destination.Length. + /// + /// Array to copy data from. + /// Position in the array from where to start copying. + /// The buffer to copy to. + /// Position in the buffer to where to start copying. + /// The number of bytes to copy. + public static void CopyTo(this byte[] source, int sourceIndex, IBuffer destination, uint destinationIndex, int count) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + CopyTo(source.AsSpan(sourceIndex, count), destination, destinationIndex); + } + #endregion (Byte []).CopyTo extensions for copying to an (IBuffer) @@ -142,110 +175,98 @@ public static byte[] ToArray(this IBuffer source, uint sourceIndex, int count) #endregion (IBuffer).ToArray extensions for copying to a new (Byte []) -#region (IBuffer).CopyTo extensions for copying to a (Byte []) - - public static void CopyTo(this IBuffer source, byte[] destination) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - - CopyTo(source, 0, destination, 0, checked((int)source.Length)); - } - - - public static void CopyTo(this IBuffer source, uint sourceIndex, byte[] destination, int destinationIndex, int count) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (destinationIndex < 0) throw new ArgumentOutOfRangeException(nameof(destinationIndex)); - if (source.Length < sourceIndex) throw new ArgumentException("The specified buffer index is not within the buffer length."); - if (source.Length - sourceIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInSourceBuffer); - if (destination.Length < destinationIndex) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_IndexOutOfArrayBounds); - if (destination.Length - destinationIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (count == 0) return; - - // If source is backed by a managed array, use the array instead of the pointer as it does not require pinning: - byte[] srcDataArr; - int srcDataOffs; - if (source.TryGetUnderlyingData(out srcDataArr, out srcDataOffs)) - { - global::System.Buffer.BlockCopy(srcDataArr, (int)(srcDataOffs + sourceIndex), destination, destinationIndex, count); - return; - } - - IntPtr srcPtr = source.GetPointerAtOffset(sourceIndex); - Marshal.Copy(srcPtr, destination, destinationIndex, count); - } - -#endregion (IBuffer).CopyTo extensions for copying to a (Byte []) - - -#region (IBuffer).CopyTo extensions for copying to an (IBuffer) - - public static void CopyTo(this IBuffer source, IBuffer destination) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - - CopyTo(source, 0, destination, 0, source.Length); - } - - - public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destination, uint destinationIndex, uint count) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (destination == null) throw new ArgumentNullException(nameof(destination)); - if (source.Length < sourceIndex) throw new ArgumentException("The specified buffer index is not within the buffer length."); - if (source.Length - sourceIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInSourceBuffer); - if (destination.Capacity < destinationIndex) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_BufferIndexExceedsCapacity); - if (destination.Capacity - destinationIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInTargetBuffer); - if (count == 0) return; - - // If source are destination are backed by managed arrays, use the arrays instead of the pointers as it does not require pinning: - byte[] srcDataArr, destDataArr; - int srcDataOffs, destDataOffs; - - bool srcIsManaged = source.TryGetUnderlyingData(out srcDataArr, out srcDataOffs); - bool destIsManaged = destination.TryGetUnderlyingData(out destDataArr, out destDataOffs); - - if (srcIsManaged && destIsManaged) - { - Debug.Assert(count <= int.MaxValue); - Debug.Assert(sourceIndex <= int.MaxValue); - Debug.Assert(destinationIndex <= int.MaxValue); - - global::System.Buffer.BlockCopy(srcDataArr!, srcDataOffs + (int)sourceIndex, destDataArr!, destDataOffs + (int)destinationIndex, (int)count); - return; - } - - IntPtr srcPtr, destPtr; - - if (srcIsManaged) - { - Debug.Assert(count <= int.MaxValue); - Debug.Assert(sourceIndex <= int.MaxValue); - - destPtr = destination.GetPointerAtOffset(destinationIndex); - Marshal.Copy(srcDataArr!, srcDataOffs + (int)sourceIndex, destPtr, (int)count); - return; - } - - if (destIsManaged) - { - Debug.Assert(count <= int.MaxValue); - Debug.Assert(destinationIndex <= int.MaxValue); - - srcPtr = source.GetPointerAtOffset(sourceIndex); - Marshal.Copy(srcPtr, destDataArr!, destDataOffs + (int)destinationIndex, (int)count); - return; - } - - srcPtr = source.GetPointerAtOffset(sourceIndex); - destPtr = destination.GetPointerAtOffset(destinationIndex); - MemCopy(srcPtr, destPtr, count); - } - +#region (IBuffer).CopyTo extensions for copying to a (Span) + + public static void CopyTo(this IBuffer source, Span destination) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + CopyTo(source, 0, destination, checked((int)source.Length)); + } + + public static void CopyTo(this IBuffer source, uint sourceIndex, Span destination, int count) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (source.Length < sourceIndex) throw new ArgumentException("The specified buffer index is not within the buffer length."); + if (source.Length - sourceIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInSourceBuffer); + if (destination.Length < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); + if (count == 0) return; + + Debug.Assert(sourceIndex <= int.MaxValue); + + Span srcSpan = source.TryGetUnderlyingData(out byte[] srcDataArr, out int srcOffset) ? srcDataArr.AsSpan(srcOffset + (int)sourceIndex, count) : source.GetSpanForCapacityUnsafe(sourceIndex); + srcSpan.CopyTo(destination); + + // Ensure source and destination stay alive for the copy operation + GC.KeepAlive(source); + } + +#endregion (IBuffer).CopyTo extensions for copying to a (Span) + +#region (IBuffer).CopyTo extensions for copying to a (Byte []) + + public static void CopyTo(this IBuffer source, byte[] destination) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + CopyTo(source, destination.AsSpan()); + } + + public static void CopyTo(this IBuffer source, uint sourceIndex, byte[] destination, int destinationIndex, int count) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + CopyTo(source, sourceIndex, destination.AsSpan(destinationIndex, count), count); + } + +#endregion (IBuffer).CopyTo extensions for copying to a (Byte []) + + +#region (IBuffer).CopyTo extensions for copying to an (IBuffer) + + public static void CopyTo(this IBuffer source, IBuffer destination) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + + CopyTo(source, 0, destination, 0, source.Length); + } + + + public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destination, uint destinationIndex, uint count) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (source.Length < sourceIndex) throw new ArgumentException("The specified buffer index is not within the buffer length."); + if (source.Length - sourceIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInSourceBuffer); + if (destination.Capacity < destinationIndex) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_BufferIndexExceedsCapacity); + if (destination.Capacity - destinationIndex < count) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientSpaceInTargetBuffer); + if (count == 0) return; + + Debug.Assert(count <= int.MaxValue); + Debug.Assert(sourceIndex <= int.MaxValue); + Debug.Assert(destinationIndex <= int.MaxValue); + + // If source are destination are backed by managed arrays, use the arrays instead of the pointers as it does not require pinning: + Span srcSpan = source.TryGetUnderlyingData(out byte[] srcDataArr, out int srcOffset) ? srcDataArr.AsSpan(srcOffset + (int)sourceIndex, (int)count) : source.GetSpanForCapacityUnsafe(sourceIndex); + Span destSpan = destination.TryGetUnderlyingData(out byte[] destDataArr, out int destOffset) ? destDataArr.AsSpan(destOffset + (int)destinationIndex) : destination.GetSpanForCapacityUnsafe(destinationIndex); + + srcSpan.CopyTo(destSpan); + + // Ensure source and destination stay alive for the copy operation + GC.KeepAlive(source); + GC.KeepAlive(destination); + + // Update Length last to make sure the data is valid + if (destinationIndex + count > destination.Length) + { + destination.Length = destinationIndex + count; + } + } + #endregion (IBuffer).CopyTo extensions for copying to an (IBuffer) @@ -440,13 +461,12 @@ public static byte GetByte(this IBuffer source, uint byteOffset) return srcDataArr[srcDataOffs + byteOffset]; } - IntPtr srcPtr = source.GetPointerAtOffset(byteOffset); - unsafe - { - // Let's avoid an unnesecary call to Marshal.ReadByte(): - byte* ptr = (byte*)srcPtr; - return *ptr; - } + Span srcSpan = source.GetSpanForCapacityUnsafe(byteOffset); + byte value = srcSpan[0]; + + // Ensure source stays alive while we read values. + GC.KeepAlive(source); + return value; } #endregion Extensions for direct by-offset access to buffer data elements @@ -596,39 +616,17 @@ public override void WriteByte(byte value) _sourceBuffer.Length = (uint)Length; } } // class WindowsRuntimeBufferUnmanagedMemoryStream - - private static IntPtr GetPointerAtOffset(this IBuffer buffer, uint offset) - { - Debug.Assert(0 <= offset); - Debug.Assert(offset < buffer.Capacity); - - unsafe - { - IntPtr buffPtr = buffer.As().Buffer; - return new IntPtr((byte*)buffPtr + offset); - } - } - - private static unsafe void MemCopy(IntPtr src, IntPtr dst, uint count) - { - if (count > int.MaxValue) - { - MemCopy(src, dst, int.MaxValue); - MemCopy(src + int.MaxValue, dst + int.MaxValue, count - int.MaxValue); - return; - } - - Debug.Assert(count <= int.MaxValue); - int bCount = (int)count; - - - // Copy via buffer. - // Note: if becomes perf critical, we will port the routine that - // copies the data without using Marshal (and byte[]) - byte[] tmp = new byte[bCount]; - Marshal.Copy(src, tmp, 0, bCount); - Marshal.Copy(tmp, 0, dst, bCount); - return; + + private static Span GetSpanForCapacityUnsafe(this IBuffer buffer, uint offset) + { + Debug.Assert(0 <= offset); + Debug.Assert(offset < buffer.Capacity); + + unsafe + { + IntPtr buffPtr = buffer.As().Buffer; + return new Span((byte*)buffPtr + offset, (int)(buffer.Capacity - offset)); + } } #endregion Private plumbing } // class WindowsRuntimeBufferExtensions