diff --git a/src/Tests/FunctionalTests/Async/Program.cs b/src/Tests/FunctionalTests/Async/Program.cs index b1e0933d0b..5219984b4e 100644 --- a/src/Tests/FunctionalTests/Async/Program.cs +++ b/src/Tests/FunctionalTests/Async/Program.cs @@ -1,11 +1,11 @@ using System; using System.IO; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using TestComponentCSharp; using Windows.Foundation; using Windows.Foundation.Tasks; +using Windows.Storage.Buffers; using Windows.Storage.Streams; using Windows.Web.Http; using WindowsRuntime.InteropServices; diff --git a/src/Tests/ObjectLifetimeTests/App.xaml.cs b/src/Tests/ObjectLifetimeTests/App.xaml.cs index de48805721..a543a935e2 100644 --- a/src/Tests/ObjectLifetimeTests/App.xaml.cs +++ b/src/Tests/ObjectLifetimeTests/App.xaml.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.Foundation; diff --git a/src/Tests/ObjectLifetimeTests/MainWindow.xaml.cs b/src/Tests/ObjectLifetimeTests/MainWindow.xaml.cs index 47bf9f206d..5ebeb659a7 100644 --- a/src/Tests/ObjectLifetimeTests/MainWindow.xaml.cs +++ b/src/Tests/ObjectLifetimeTests/MainWindow.xaml.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; diff --git a/src/Tests/ObjectLifetimeTests/MyControl.xaml.cs b/src/Tests/ObjectLifetimeTests/MyControl.xaml.cs index fee8e88374..d26c04b978 100644 --- a/src/Tests/ObjectLifetimeTests/MyControl.xaml.cs +++ b/src/Tests/ObjectLifetimeTests/MyControl.xaml.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Microsoft.UI.Xaml; diff --git a/src/Tests/ObjectLifetimeTests/ObjectLifetimePage.xaml.cs b/src/Tests/ObjectLifetimeTests/ObjectLifetimePage.xaml.cs index 753ee1d68b..376c2b91c4 100644 --- a/src/Tests/ObjectLifetimeTests/ObjectLifetimePage.xaml.cs +++ b/src/Tests/ObjectLifetimeTests/ObjectLifetimePage.xaml.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Microsoft.UI.Xaml; diff --git a/src/Tests/UnitTest/ApiCompatTests.cs b/src/Tests/UnitTest/ApiCompatTests.cs index 24c79c74ab..5705031cce 100644 --- a/src/Tests/UnitTest/ApiCompatTests.cs +++ b/src/Tests/UnitTest/ApiCompatTests.cs @@ -21,11 +21,11 @@ using Windows.Data.Json; using Windows.Storage; +using Windows.Storage.Buffers; using Windows.Storage.Streams; using Windows.Storage.FileProperties; using System.Diagnostics; -using System.Runtime.InteropServices.WindowsRuntime; namespace UnitTest { diff --git a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs index f68d3d66ff..b228fc54a2 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -13,7 +13,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Runtime.InteropServices.WindowsRuntime; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -36,6 +35,7 @@ using Windows.Security.Cryptography; using Windows.Security.Cryptography.Core; using Windows.Storage; +using Windows.Storage.Buffers; using Windows.Storage.Streams; using Windows.UI; using Windows.UI.Notifications; diff --git a/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs new file mode 100644 index 0000000000..c1908daa7a --- /dev/null +++ b/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using WindowsRuntime; +using WindowsRuntime.InteropServices; +using WindowsRuntime.InteropServices.Marshalling; +using static System.Runtime.InteropServices.ComWrappers; + +#pragma warning disable CS0723, IDE0008, IDE0046, IDE1006 + +[assembly: TypeMapAssociation( + source: typeof(WindowsRuntimeExternalArrayBuffer), + proxy: typeof(ABI.WindowsRuntime.InteropServices.WindowsRuntimeExternalArrayBuffer))] + +namespace ABI.WindowsRuntime.InteropServices; + +/// +/// ABI type for . +/// +[WindowsRuntimeClassName("Windows.Storage.Streams.IBuffer")] +[WindowsRuntimeExternalArrayBufferComWrappersMarshaller] +file static class WindowsRuntimeExternalArrayBuffer; + +/// +/// The set of values for . +/// +file struct WindowsRuntimeExternalArrayBufferInterfaceEntries +{ + public ComInterfaceEntry IBuffer; + public ComInterfaceEntry IBufferByteAccess; + public ComInterfaceEntry IStringable; + public ComInterfaceEntry IWeakReferenceSource; + public ComInterfaceEntry IMarshal; + public ComInterfaceEntry IAgileObject; + public ComInterfaceEntry IInspectable; + public ComInterfaceEntry IUnknown; +} + +/// +/// The implementation of . +/// +file static class WindowsRuntimeExternalArrayBufferInterfaceEntriesImpl +{ + /// + /// The value for . + /// + [FixedAddressValueType] + public static readonly WindowsRuntimeExternalArrayBufferInterfaceEntries Entries; + + /// + /// Initializes . + /// + static WindowsRuntimeExternalArrayBufferInterfaceEntriesImpl() + { + Entries.IBuffer.IID = WellKnownWindowsInterfaceIIDs.IID_IBuffer; + Entries.IBuffer.Vtable = Windows.Storage.Streams.IBufferImpl.Vtable; + Entries.IBufferByteAccess.IID = WellKnownWindowsInterfaceIIDs.IID_IBufferByteAccess; + Entries.IBufferByteAccess.Vtable = WindowsRuntimeExternalArrayBufferByteAccessImpl.Vtable; + Entries.IStringable.IID = WellKnownWindowsInterfaceIIDs.IID_IStringable; + Entries.IStringable.Vtable = IStringableImpl.Vtable; + Entries.IWeakReferenceSource.IID = WellKnownWindowsInterfaceIIDs.IID_IWeakReferenceSource; + Entries.IWeakReferenceSource.Vtable = IWeakReferenceSourceImpl.Vtable; + Entries.IMarshal.IID = WellKnownWindowsInterfaceIIDs.IID_IMarshal; + Entries.IMarshal.Vtable = IMarshalImpl.RoBufferVtable; + Entries.IAgileObject.IID = WellKnownWindowsInterfaceIIDs.IID_IAgileObject; + Entries.IAgileObject.Vtable = IAgileObjectImpl.Vtable; + Entries.IInspectable.IID = WellKnownWindowsInterfaceIIDs.IID_IInspectable; + Entries.IInspectable.Vtable = IInspectableImpl.Vtable; + Entries.IUnknown.IID = WellKnownWindowsInterfaceIIDs.IID_IUnknown; + Entries.IUnknown.Vtable = IUnknownImpl.Vtable; + } +} + +/// +/// A custom implementation for . +/// +[Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, + DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, + UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed unsafe class WindowsRuntimeExternalArrayBufferComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute +{ + /// + public override void* GetOrCreateComInterfaceForObject(object value) + { + // No reference tracking is needed, see notes in the marshaller attribute for 'WindowsRuntimePinnedArrayBuffer' + return (void*)WindowsRuntimeComWrappers.Default.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.None); + } + + /// + public override ComInterfaceEntry* ComputeVtables(out int count) + { + count = sizeof(WindowsRuntimeExternalArrayBufferInterfaceEntries) / sizeof(ComInterfaceEntry); + + return (ComInterfaceEntry*)Unsafe.AsPointer(in WindowsRuntimeExternalArrayBufferInterfaceEntriesImpl.Entries); + } +} + +/// +/// The native implementation of IBufferByteAccess for . +/// +file static unsafe class WindowsRuntimeExternalArrayBufferByteAccessImpl +{ + /// + /// The value for the implementation. + /// + [FixedAddressValueType] + private static readonly IBufferByteAccessVftbl Vftbl; + + /// + /// Initializes . + /// + static WindowsRuntimeExternalArrayBufferByteAccessImpl() + { + *(IInspectableVftbl*)Unsafe.AsPointer(ref Vftbl) = *(IInspectableVftbl*)IInspectableImpl.Vtable; + + Vftbl.Buffer = &Buffer; + } + + /// + /// Gets a pointer to the implementation. + /// + public static nint Vtable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (nint)Unsafe.AsPointer(in Vftbl); + } + + /// + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] + private static HRESULT Buffer(void* thisPtr, byte** value) + { + if (value is null) + { + return WellKnownErrorCodes.E_POINTER; + } + + try + { + var thisObject = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); + + *value = thisObject.Buffer(); + + return WellKnownErrorCodes.S_OK; + } + catch (Exception ex) + { + return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); + } + } +} \ No newline at end of file diff --git a/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index aef30f533c..46dfaab91e 100644 --- a/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -145,7 +145,7 @@ private static HRESULT Buffer(void* thisPtr, byte** value) { var thisObject = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr); - *value = thisObject.Buffer; + *value = thisObject.Buffer(); return WellKnownErrorCodes.S_OK; } diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs new file mode 100644 index 0000000000..20a187ad8e --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage.Streams; + +namespace WindowsRuntime.InteropServices; + +/// +/// A implementation backed by a managed instance. +/// +internal sealed class WindowsRuntimeBufferMemoryStream : MemoryStream +{ + /// + /// The instance to back the stream. + /// + private readonly IBuffer _buffer; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The instance to back the stream. + /// The byte array to use as the underlying storage. + /// The offset in the byte array where the stream starts. + /// This constructor doesn't validate any of its parameters. + public WindowsRuntimeBufferMemoryStream(IBuffer buffer, byte[] array, int offset) + : base(array, offset, (int)buffer.Capacity, writable: true) + { + _buffer = buffer; + + SetLength(buffer.Length); + } + + /// + public override void SetLength(long value) + { + base.SetLength(value); + + // The input value is limited by 'Capacity', so this cast is safe + _buffer.Length = (uint)Length; + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + base.Write(buffer, offset, count); + + _buffer.Length = (uint)Length; + } + + /// + public override void Write(ReadOnlySpan buffer) + { + base.Write(buffer); + + _buffer.Length = (uint)Length; + } + + /// + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await base.WriteAsync(buffer, offset, count, cancellationToken); + + _buffer.Length = (uint)Length; + } + + /// + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + await base.WriteAsync(buffer, cancellationToken); + + _buffer.Length = (uint)Length; + } + + /// + public override void WriteByte(byte value) + { + base.WriteByte(value); + + _buffer.Length = (uint)Length; + } +} diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferUnmanagedMemoryStream.cs b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferUnmanagedMemoryStream.cs new file mode 100644 index 0000000000..b5af9aaaa3 --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferUnmanagedMemoryStream.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage.Streams; + +namespace WindowsRuntime.InteropServices; + +/// +/// A implementation backed by a native instance. +/// +internal sealed class WindowsRuntimeBufferUnmanagedMemoryStream : UnmanagedMemoryStream +{ + /// + /// The instance to back the stream. + /// + private readonly IBuffer _buffer; + + /// + /// Creates a new instance with the specified parameters. + /// + /// The instance to back the stream. + /// The native memory to use as the underlying storage. + /// This constructor doesn't validate any of its parameters. + public unsafe WindowsRuntimeBufferUnmanagedMemoryStream(IBuffer buffer, byte* data) + : base(data, buffer.Length, buffer.Capacity, FileAccess.ReadWrite) + { + _buffer = buffer; + } + + /// + public override void SetLength(long value) + { + base.SetLength(value); + + // The input value is limited by 'Capacity', so this cast is safe + _buffer.Length = (uint)Length; + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + base.Write(buffer, offset, count); + + _buffer.Length = (uint)Length; + } + + /// + public override void Write(ReadOnlySpan buffer) + { + base.Write(buffer); + + _buffer.Length = (uint)Length; + } + + /// + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await base.WriteAsync(buffer.AsMemory(offset, count), cancellationToken); + + _buffer.Length = (uint)Length; + } + + /// + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + await base.WriteAsync(buffer, cancellationToken); + + _buffer.Length = (uint)Length; + } + + /// + public override void WriteByte(byte value) + { + base.WriteByte(value); + + _buffer.Length = (uint)Length; + } +} diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs new file mode 100644 index 0000000000..a7abaa8012 --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using Windows.Storage.Streams; + +namespace WindowsRuntime.InteropServices; + +/// +/// Provides a managed implementation of the interface backed by an external array. +/// +[WindowsRuntimeManagedOnlyType] +internal sealed unsafe class WindowsRuntimeExternalArrayBuffer : IBuffer +{ + /// + /// The external array to use. + /// + private readonly byte[] _data; + + /// + /// The offset in . + /// + private readonly int _offset; + + /// + /// The number of bytes that can be read or written in . + /// + private int _length; + + /// + /// The capacity of the buffer. + /// + private readonly int _capacity; + + /// + /// The pinned GC handle to pin . + /// + private PinnedGCHandle _pinnedHandle; + + /// + /// The address of the pinned data, at the right offset, if has been allocated. + /// + private nint _pinnedDataPtr; + + /// + /// Creates a instance with the specified parameters. + /// + /// The external array to wrap. + /// The offset in . + /// The number of bytes. + /// The maximum number of bytes the buffer can hold. + /// This constructor doesn't validate any of its parameters. + public WindowsRuntimeExternalArrayBuffer(byte[] data, int offset, int length, int capacity) + { + Debug.Assert(data is not null); + Debug.Assert(offset >= 0); + Debug.Assert(length >= 0); + Debug.Assert(capacity >= 0); + Debug.Assert(data.Length - offset >= length); + Debug.Assert(data.Length - offset >= capacity); + Debug.Assert(capacity >= length); + + _data = data; + _offset = offset; + _length = length; + _capacity = capacity; + _pinnedHandle = default; + _pinnedDataPtr = 0; + } + + /// + /// Finalizes the current instance. + /// + ~WindowsRuntimeExternalArrayBuffer() + { + _pinnedHandle.Dispose(); + } + + /// + public uint Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (uint)_capacity; + } + + /// + public uint Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (uint)_length; + set + { + ArgumentOutOfRangeException.ThrowIfBufferLengthExceedsCapacity(value, Capacity); + + _length = unchecked((int)value); + } + } + + /// + public byte* Buffer() + { + // Try to get the (already computed) pointer to the offset data + nint pinnedDataPtr = Volatile.Read(ref _pinnedDataPtr); + + // If the pointer is not 'null', it means another caller has already + // called this method, and subsequently caused the array to be pinned. + if (pinnedDataPtr != 0) + { + return (byte*)pinnedDataPtr; + } + + // If we're potentially the first ones to get here, then try to pin the array. + // This method will return the right pointer with the offset already computed. + return PinData(); + } + + /// + public byte[] GetArray(out int offset) + { + offset = _offset; + + return _data; + } + + /// + public ArraySegment GetArraySegment() + { + return new(_data, _offset, _length); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpanForCapacity() + { + ref byte pinnedData = ref MemoryMarshal.GetArrayDataReference(_data); + + // See notes in 'WindowsRuntimePinnedArrayBuffer.GetSpan' for why skipping checks here is valid + return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); + } + + /// + /// Pins and returns the address of its data at the right offset. + /// + /// The address of the pinned data to use. + private byte* PinData() + { + PinnedGCHandle pinnedHandle = default; + nint pinnedDataPtr; + bool wasPinnedDataPtrWritten = false; + + try + { + // Pin the data array + pinnedHandle = new PinnedGCHandle(_data); + pinnedDataPtr = (nint)(&pinnedHandle.GetAddressOfArrayData()[_offset]); + + // Store the address only if it hasn't been assigned yet. This operation is why we + // have to use 'nint' for the field instead of 'byte*', as no overload supports it. + wasPinnedDataPtrWritten = Interlocked.CompareExchange( + location1: ref _pinnedDataPtr, + value: pinnedDataPtr, + comparand: 0) == 0; + } + finally + { + // Track the pinned GC handle so that we can release it from the finalizer + if (wasPinnedDataPtrWritten) + { + _pinnedHandle = pinnedHandle; + } + else + { + // If we have a race with another thread also trying to pin the array, and that thread is the + // first one to assign the pinned data pointer, then just stop and dispose the pinned GC handle + // we just created. It's not needed anyway, as the one from the other thread will remain active. + pinnedHandle.Dispose(); + } + } + + // Return the address we retrieved, it will point to the pinned array data + return (byte*)pinnedDataPtr; + } +} diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index e5017780f7..8fbce18f64 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -97,19 +97,49 @@ public uint Length /// /// Gets the array of bytes in the buffer. /// + /// The byte array. /// - internal unsafe byte* Buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe byte* Buffer() { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => &((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(_pinnedData)))[(uint)_offset]; + return &((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(_pinnedData)))[(uint)_offset]; + } + + /// + /// Gets the underlying array for this buffer. + /// + /// The offset in the returned array. + /// The underlying array. + public byte[] GetArray(out int offset) + { + offset = _offset; + + return _pinnedData; } /// /// Gets an value for the underlying data in this buffer. /// /// The resulting value. - internal ArraySegment GetArraySegment() + public ArraySegment GetArraySegment() { return new(_pinnedData, _offset, _length); } + + /// + /// Gets a value for the underlying data in this buffer. + /// + /// The resulting value. + /// + /// The returned value has a length equal to , not . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpanForCapacity() + { + ref byte pinnedData = ref MemoryMarshal.GetArrayDataReference(_pinnedData); + + // All parameters have been validated before constructing this object, + // so we can avoid the overhead of checking the offset and capacity. + return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); + } } diff --git a/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs b/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs index 8cac8ccbe0..c07a91b0b8 100644 --- a/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs +++ b/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs @@ -48,10 +48,18 @@ public static unsafe bool TryGetDataUnsafe([NotNullWhen(true)] IBuffer? buffer, return true; } - // Also handle a managed instance of the pinned array buffer type from 'WinRT.Runtime.dll' + // Also handle a managed instance of the external array buffer type from 'WinRT.Runtime.dll' + if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + { + data = externalArrayBuffer.Buffer(); + + return true; + } + + // Same as above, but for pinned array buffers as well if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) { - data = pinnedArrayBuffer.Buffer; + data = pinnedArrayBuffer.Buffer(); return true; } @@ -119,6 +127,14 @@ public static bool TryGetArray([NotNullWhen(true)] IBuffer? buffer, out ArraySeg } // If buffer is backed by a managed array, return it + if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + { + array = externalArrayBuffer.GetArraySegment(); + + return true; + } + + // Same as above for pinned arrays as well if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) { array = pinnedArrayBuffer.GetArraySegment(); diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs index 911cc2d947..73df466117 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs @@ -297,6 +297,26 @@ public static ArgumentOutOfRangeException GetArgumentOutOfRangeException(string? { return new ArgumentOutOfRangeException(paramName); } + + /// + /// Throws an if the specified buffer exceeds . + /// + /// The buffer length to check. + /// Thrown if exceeds . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfBufferLengthExceedsArrayMaxLength(uint length) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentOutOfRangeException() + => throw new ArgumentOutOfRangeException(null, WindowsRuntimeExceptionMessages.ArgumentOutOfRange_BufferLengthExceedsArrayMaxLength); + + if (length > Array.MaxLength) + { + ThrowArgumentOutOfRangeException(); + } + } } extension(KeyNotFoundException) @@ -385,6 +405,201 @@ static void ThrowArgumentException() ThrowArgumentException(); } } + + /// + /// Throws an if the array does not have enough elements after the specified . + /// + /// The total length of the array. + /// The offset into the array. + /// The number of required elements after the offset. + /// Thrown if minus is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfInsufficientArrayElementsAfterOffset(int totalLength, int offset, int required) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InsufficientArrayElementsAfterOffset); + + if (totalLength - offset < required) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if the buffer capacity is insufficient for the specified length. + /// + /// The buffer capacity. + /// The required length. + /// Thrown if is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfInsufficientBufferCapacity(int capacity, int length) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InsufficientBufferCapacity); + + if (capacity < length) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if the specified buffer exceeds the buffer . + /// + /// The buffer index to check. + /// The buffer capacity. + /// Thrown if is greater than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfBufferIndexExceedsCapacity(uint index, uint capacity) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_BufferIndexExceedsCapacity); + + if (index > capacity) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if the specified buffer exceeds the buffer . + /// + /// The buffer index to check. + /// The buffer length. + /// Thrown if is greater than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfBufferIndexExceedsLength(uint index, uint length) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_BufferIndexExceedsLength); + + if (index > length) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if the specified buffer is not within the buffer . + /// + /// The buffer offset to check. + /// The buffer length. + /// Thrown if is greater than or equal to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfBufferOffsetOutOfRange(uint offset, uint length) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_BufferOffsetExceedsLength); + + if (offset >= length) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if there is insufficient space in the target buffer. + /// + /// The total capacity of the target buffer. + /// The starting index in the target buffer. + /// The number of elements to write. + /// Thrown if minus is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfInsufficientSpaceInTargetBuffer(uint capacity, uint index, uint required) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InsufficientSpaceInTargetBuffer); + + if (capacity - index < required) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if there is insufficient space in the source buffer. + /// + /// The total length of the source buffer. + /// The starting index in the source buffer. + /// The number of elements to read. + /// Thrown if minus is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfInsufficientSpaceInSourceBuffer(uint length, uint index, uint required) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InsufficientSpaceInSourceBuffer); + + if (length - index < required) + { + ThrowArgumentException(); + } + } + + /// + /// Throws an if the specified stream position is beyond the end of the stream. + /// + /// The length of the stream. + /// The position to check. + /// Thrown if is less than . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StackTraceHidden] + public static void ThrowIfStreamPositionBeyondEndOfStream(long streamLength, int position) + { + [DoesNotReturn] + [StackTraceHidden] + static void ThrowArgumentException() + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_StreamPositionBeyondEndOfStream); + + if (streamLength < position) + { + ThrowArgumentException(); + } + } + + /// + /// Creates an indicating that the provided IBuffer instance is not valid. + /// + /// The resulting instance. + [MethodImpl(MethodImplOptions.NoInlining)] + public static ArgumentException GetInvalidIBufferInstanceException() + { + return new(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + } + } + + extension(UnauthorizedAccessException) + { + /// + /// Throws an indicating that the internal buffer of a cannot be accessed. + /// + /// Always thrown. + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowInternalBufferAccess() + { + throw new UnauthorizedAccessException(WindowsRuntimeExceptionMessages.UnauthorizedAccess_InternalBuffer); + } } extension(Win32Exception) diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs index fcfd6c39f1..c5fc782f31 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs @@ -14,12 +14,32 @@ internal static class WindowsRuntimeExceptionMessages public const string Argument_AddingDuplicate = "An item with the same key has already been added."; + public const string Argument_BufferIndexExceedsCapacity = "The specified buffer index exceeds the buffer capacity."; + + public const string Argument_BufferIndexExceedsLength = "The specified buffer index exceeds the buffer length."; + + public const string Argument_BufferOffsetExceedsLength = "The specified buffer offset exceeds the buffer length."; + public const string Argument_BufferLengthExceedsCapacity = "The specified useful data length exceeds the capacity of this buffer."; public const string Argument_IndexOutOfArrayBounds = "The specified index is out of bounds of the specified array."; + public const string Argument_InsufficientArrayElementsAfterOffset = "The specified array does not contain the specified number of elements starting at the specified offset."; + + public const string Argument_InsufficientBufferCapacity = "The buffer capacity is insufficient for the specified length."; + public const string Argument_InsufficientSpaceToCopyCollection = "Insufficient space in the target array to copy the collection."; + public const string Argument_InsufficientSpaceInSourceBuffer = "Insufficient space in the source buffer."; + + public const string Argument_InsufficientSpaceInTargetBuffer = "Insufficient space in the target buffer."; + + public const string Argument_InvalidIBufferInstance = "The provided 'IBuffer' instance is not valid, and retrieving its underlying data failed."; + + public const string Argument_StreamPositionBeyondEndOfStream = "The specified position is beyond the end of the stream."; + + public const string ArgumentOutOfRange_BufferLengthExceedsArrayMaxLength = "The specified buffer length exceeds the maximum array length."; + public const string ArgumentOutOfRange_Index = "Index was out of range."; public const string ArgumentOutOfRange_IndexLargerThanMaxValue = "The specified index is larger than the maximum allowed value."; @@ -42,5 +62,7 @@ internal static class WindowsRuntimeExceptionMessages public const string ObjectDisposed_AsyncInfoIsClosed = "The requested invocation is not permitted because this 'IAsyncInfo' instance has already been closed."; + public const string UnauthorizedAccess_InternalBuffer = "The underlying buffer of the 'MemoryStream' cannot be accessed."; + public const string WinRtCOM_Error = "An error has occurred."; } \ No newline at end of file diff --git a/src/WinRT.Runtime2/Windows.Storage.Streams/Buffers/WindowsRuntimeBuffer.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBuffer.cs similarity index 100% rename from src/WinRT.Runtime2/Windows.Storage.Streams/Buffers/WindowsRuntimeBuffer.cs rename to src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBuffer.cs diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs new file mode 100644 index 0000000000..e97677b4b5 --- /dev/null +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -0,0 +1,712 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Windows.Storage.Streams; +using WindowsRuntime; +using WindowsRuntime.InteropServices; + +#pragma warning disable IDE0057 + +namespace Windows.Storage.Buffers; + +/// +/// Provides extension methods that expose operations on objects. +/// +public static class WindowsRuntimeBufferExtensions +{ + /// + /// Returns an instance that represents the specified byte array. + /// + /// The byte array to represent. + /// The resulting instance. + /// + /// The returned instance will have and equal to the length of . + /// + /// Thrown if is . + public static IBuffer AsBuffer(this byte[] source) + { + return AsBuffer(source, offset: 0, length: source.Length, capacity: source.Length); + } + + /// + /// Returns an instance that represents a range of bytes in the specified byte array. + /// + /// The byte array to represent. + /// The offset in where the range begins. + /// The length of the range that is represented by the instance. + /// The resulting instance that represents the specified range of bytes in . + /// Thrown if is . + /// Thrown if or are less than 0. + /// Thrown if the specified range is not valid. + public static IBuffer AsBuffer(this byte[] source, int offset, int length) + { + return AsBuffer(source, offset: offset, length: length, capacity: length); + } + + /// + /// Returns an instance that represents a range of bytes in the specified byte array and has a specified capacity. + /// + /// The byte array to represent. + /// The offset in where the range begins. + /// The length of the range that is represented by the instance. + /// The value to use for the property on the returned instance. + /// The resulting instance that represents the specified range of bytes in . + /// Thrown if is . + /// Thrown if , , or are less than 0. + /// Thrown if the specified range is not valid, or if is less than the specified range. + public static IBuffer AsBuffer(this byte[] source, int offset, int length, int capacity) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfNegative(length); + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + ArgumentException.ThrowIfInsufficientArrayElementsAfterOffset(source.Length, offset, length); + ArgumentException.ThrowIfInsufficientArrayElementsAfterOffset(source.Length, offset, capacity); + ArgumentException.ThrowIfInsufficientBufferCapacity(capacity, length); + + return new WindowsRuntimeExternalArrayBuffer(source, offset, length, capacity); + } + + /// + /// Copies the contents of a given value to a target instance. + /// + /// The value to copy from. + /// The destination instance to copy data to. + /// + /// This method will update the property of if copying the data + /// exceeds the current value of that property (but still falls within the bounds of ). If the + /// data being copied fits within the current value of , its value is not modified. + /// + /// Thrown if is . + /// Thrown if does not have enough capacity for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this ReadOnlySpan source, IBuffer destination) + { + CopyTo(source, destination, destinationIndex: 0); + } + + /// + /// Copies the contents of a given value to a target instance. + /// + /// The value to copy from. + /// The destination instance to copy data to. + /// The index within from which to start copying data to. + /// + /// This method will update the property of if copying the data + /// exceeds the current value of that property (but still falls within the bounds of ). If the + /// data being copied fits within the current value of , its value is not modified. + /// + /// Thrown if is . + /// + /// Thrown if exceeds the value of the property for , + /// or if the remaining space starting at the specified index is not enough for the copy operation. + /// + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this ReadOnlySpan source, IBuffer destination, uint destinationIndex) + { + ArgumentNullException.ThrowIfNull(destination); + ArgumentException.ThrowIfBufferIndexExceedsCapacity(destinationIndex, destination.Capacity); + ArgumentException.ThrowIfInsufficientSpaceInTargetBuffer(destination.Capacity, destinationIndex, (uint)source.Length); + + // If the source span is empty, just stop here immediately and skip all overhead of preparing the target range + if (source.IsEmpty) + { + return; + } + + Debug.Assert(destinationIndex <= int.MaxValue); + + Span destinationSpan = GetSpanForCapacity(destination).Slice(start: (int)destinationIndex); + + source.CopyTo(destinationSpan); + + // Ensure the destination buffer stays alive for the copy operation. This is required because + // 'IBuffer' implementations might release their memory immediately when collected. The span + // we have retrieved will not be enough to keep the actual owning buffer instances alive. + GC.KeepAlive(destination); + + // Update the 'Length' property last to make sure the data is valid + if (destinationIndex + source.Length > destination.Length) + { + destination.Length = destinationIndex + (uint)source.Length; + } + } + + /// + /// Copies the contents of a given byte array to a target instance. + /// + /// The byte array to copy from. + /// The destination instance to copy data to. + /// + /// This method will update the property of if copying the data + /// exceeds the current value of that property (but still falls within the bounds of ). If the + /// data being copied fits within the current value of , its value is not modified. + /// + /// Thrown if or are . + /// Thrown if does not have enough capacity for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this byte[] source, IBuffer destination) + { + ArgumentNullException.ThrowIfNull(source); + + CopyTo(source.AsSpan(), destination, destinationIndex: 0); + } + + /// + /// Copies a range of bytes in the specified byte array to a target instance. + /// + /// The byte array to copy from. + /// The index in to begin copying data from. + /// The destination instance to copy data to. + /// The index within from which to start copying data to. + /// The number of bytes to copy. + /// + /// This method will update the property of if copying the data + /// exceeds the current value of that property (but still falls within the bounds of ). If the + /// data being copied fits within the current value of , its value is not modified. + /// + /// Thrown if or are . + /// + /// Thrown if is less than 0, if it exceeds the length of , + /// or if exceeds the capacity of . + /// + /// + /// Thrown if exceeds the value of the property for , + /// or if the remaining space starting at the specified index is not enough for the copy operation. + /// + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this byte[] source, int sourceIndex, IBuffer destination, uint destinationIndex, int count) + { + ArgumentNullException.ThrowIfNull(source); + + CopyTo(source.AsSpan(start: sourceIndex, length: count), destination, destinationIndex: destinationIndex); + } + + /// + /// Copies the contents of a given instance to a target value. + /// + /// The instance to copy from. + /// The destination value to copy data to. + /// Thrown if is . + /// Thrown if does not have enough capacity for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this IBuffer source, Span destination) + { + CopyTo(source, sourceIndex: 0, destination, count: checked((int)source.Length)); + } + + /// + /// Copies a range of bytes of a given instance to a target value. + /// + /// The instance to copy from. + /// The index in to begin copying data from. + /// The destination value to copy data to. + /// The number of bytes to copy. + /// Thrown if is . + /// + /// Thrown if is less than 0, if it exceeds the length of , + /// or if exceeds the length of . + /// + /// Thrown if the remaining space starting at the specified index is not enough for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this IBuffer source, uint sourceIndex, Span destination, int count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentOutOfRangeException.ThrowIfNegative(count); + ArgumentException.ThrowIfBufferIndexExceedsLength(sourceIndex, source.Length); + ArgumentException.ThrowIfInsufficientSpaceInSourceBuffer(source.Length, sourceIndex, (uint)count); + ArgumentException.ThrowIfInsufficientArrayElementsAfterOffset(destination.Length, 0, count); + + // If there are no values to copy, just stop here immediately and skip all overhead of preparing the target range + if (count == 0) + { + return; + } + + Debug.Assert(sourceIndex <= int.MaxValue); + + Span sourceSpan = GetSpanForCapacity(source).Slice(start: (int)sourceIndex, length: count); + + sourceSpan.CopyTo(destination); + + GC.KeepAlive(source); + } + + /// + /// Copies the contents of a given instance to a target byte array. + /// + /// The instance to copy from. + /// The destination byte array to copy data to. + /// Thrown if or are . + /// Thrown if does not have enough capacity for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this IBuffer source, byte[] destination) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(destination); + + CopyTo(source, destination.AsSpan()); + } + + /// + /// Copies a range of bytes of a given instance to a target byte array. + /// + /// The instance to copy from. + /// The index in to begin copying data from. + /// The destination byte array to copy data to. + /// The index within from which to start copying data to. + /// The number of bytes to copy. + /// Thrown if or are . + /// + /// Thrown if is less than 0, if it exceeds the length of , + /// or if exceeds the length of . + /// + /// Thrown if the remaining space starting at the specified index is not enough for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static void CopyTo(this IBuffer source, uint sourceIndex, byte[] destination, int destinationIndex, int count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(destination); + + CopyTo(source, sourceIndex: sourceIndex, destination.AsSpan(destinationIndex, count), count: count); + } + + /// + /// Copies all bytes from the source instance to the destination instance, starting at offset 0 in both. + /// + /// The instance to copy from. + /// The destination instance to copy data to. + /// Thrown if or are . + /// Thrown if does not have enough capacity for the copy operation. + /// Thrown if invoking IBufferByteAccess.Buffer on either input buffer fails. + public static void CopyTo(this IBuffer source, IBuffer destination) + { + ArgumentNullException.ThrowIfNull(source); + + CopyTo(source, 0, destination, 0, source.Length); + } + + /// + /// Copies a range of bytes from the source instance to the destination instance. + /// + /// The instance to copy from. + /// The index in to begin copying data from. + /// The destination instance to copy data to. + /// The index within from which to start copying data to. + /// The number of bytes to copy. + /// Thrown if or are . + /// + /// Thrown if exceeds the value of the property for , + /// if exceeds the value of the property for , + /// or if the remaining space starting at the specified index is not enough for the copy operation. + /// + /// Thrown if invoking IBufferByteAccess.Buffer on either input buffer fails. + public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destination, uint destinationIndex, uint count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(destination); + ArgumentException.ThrowIfBufferIndexExceedsLength(sourceIndex, source.Length); + ArgumentException.ThrowIfInsufficientSpaceInSourceBuffer(source.Length, sourceIndex, count); + ArgumentException.ThrowIfBufferIndexExceedsCapacity(destinationIndex, destination.Capacity); + ArgumentException.ThrowIfInsufficientSpaceInTargetBuffer(destination.Capacity, destinationIndex, count); + + // If there are no values to copy, just stop here immediately and skip all overhead of preparing the target range + if (count == 0) + { + return; + } + + Debug.Assert(count <= int.MaxValue); + Debug.Assert(sourceIndex <= int.MaxValue); + Debug.Assert(destinationIndex <= int.MaxValue); + + Span sourceSpan = GetSpanForCapacity(source).Slice(start: (int)sourceIndex, length: (int)count); + Span destinationSpan = GetSpanForCapacity(destination).Slice(start: (int)destinationIndex); + + sourceSpan.CopyTo(destinationSpan); + + GC.KeepAlive(source); + GC.KeepAlive(destination); + + // Update the 'Length' property last to make sure the data is valid + if (destinationIndex + count > destination.Length) + { + destination.Length = destinationIndex + count; + } + } + + /// + /// Returns a new array that is created from the contents of the specified instance. + /// The size of the array is the value of the property of the input buffer. + /// + /// The instance whose contents will be used to populate the new array. + /// A byte array that contains the bytes in , beginning at offset 0. + /// Thrown if is . + /// Thrown if the length of exceeds . + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static byte[] ToArray(this IBuffer source) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentOutOfRangeException.ThrowIfBufferLengthExceedsArrayMaxLength(source.Length); + + return ToArray(source, sourceIndex: 0, count: (int)source.Length); + } + + /// + /// Returns a new array that is created from the contents of the specified + /// instance, starting at a specified offset and including a specified number of bytes. + /// + /// The instance whose contents will be used to populate the new array. + /// The index in to begin copying data from. + /// The number of bytes to copy. + /// A byte array that contains the specified range of bytes. + /// Thrown if is . + /// Thrown if is less than 0. + /// + /// Thrown if exceeds the value of the property for , + /// or if the remaining space starting at the specified index is not enough for the copy operation. + /// + /// Thrown if invoking IBufferByteAccess.Buffer on the input buffer fails. + public static byte[] ToArray(this IBuffer source, uint sourceIndex, int count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentOutOfRangeException.ThrowIfNegative(count); + ArgumentException.ThrowIfBufferIndexExceedsLength(sourceIndex, source.Length); + ArgumentException.ThrowIfInsufficientSpaceInSourceBuffer(source.Length, sourceIndex, (uint)count); + + // If the specified length is just '0', we can return a cached empty array + if (count == 0) + { + return []; + } + + byte[] destination = GC.AllocateUninitializedArray(count); + + source.CopyTo(sourceIndex: sourceIndex, destination, destinationIndex: 0, count: count); + + return destination; + } + + /// + /// Checks if the underlying memory backing two instances is actually the same memory. + /// + /// The first instance. + /// The second instance. + /// Whether the underlying memory pointer is the same for both specified instances (i.e. if they're backed by the same memory). + /// + /// + /// When applied to instances backed by managed arrays, this method is preferable to a naive comparison + /// (such as via ), because it avoids pinning + /// the backing array which would be necessary if trying to retrieve a native memory pointer. + /// + /// + /// This method will not take the and properties into account. + /// + /// + /// Thrown if is . + /// Thrown if invoking IBufferByteAccess.Buffer on either input buffer fails. + public static unsafe bool IsSameData(this IBuffer buffer, [NotNullWhen(true)] IBuffer? otherBuffer) + { + ArgumentNullException.ThrowIfNull(buffer); + + if (otherBuffer is null) + { + return false; + } + + if (buffer == otherBuffer) + { + return true; + } + + bool bufferIsManaged = TryGetArray(buffer, out byte[]? array, out int offset); + bool otherBufferIsManaged = TryGetArray(otherBuffer, out byte[]? otherArray, out int otherOffset); + + // If only one of the two input buffers is backed by managed memory, they can't possibly be equal + if (bufferIsManaged != otherBufferIsManaged) + { + return false; + } + + // If both buffers are backed by managed memory, check whether they're pointing to the same area. Note that this could return 'true' + // even if the actual managed buffer types are different. For instance, one could be a 'WindowsRuntimePinnedArrayBuffer', which the + // user then unwrapped via 'WindowsRuntimeBufferMarshal.TryGetArray', and then used to create a separate managed buffer instance, + // by calling one of the 'AsBuffer' extensions defined above. So the only thing we can do is to compare the actual array ranges. + if (bufferIsManaged) + { + return (array == otherArray) && (offset == otherOffset); + } + + // Lastly, check whether they're both native buffer objects that point to the same memory. Here we're intentionally not reusing + // the 'TryGetNativeSpanForCapacity', because that method also does a range check for the 'Capacity' property. For the purposes + // of this method, we actually don't want that. Two buffers should just compare as equal even if their capacity exceeds the limit + // for managed spans. That is fine here, given we're not actually passing that span anywhere (and this method shouldn't throw). + if (TryGetData(buffer, out byte* data) && TryGetData(otherBuffer, out byte* otherData)) + { + return data == otherData; + } + + // If we got here, it means the buffer is some unrecognized instance we don't know how to unwrap. + // Since we're just interested in checking whether the data is the same, we don't need to throw. + return false; + } + + /// + /// Creates a new instance backed by the same memory as the specified instance. + /// + /// The to use to share the data memory with the buffer being created. + /// A new instance backed by the same memory as . + /// + /// The instance may re-sized in future, which would cause that stream to be backed by a different memory region. + /// In that scenario, the buffer created by this method will remain backed by the memory behind the stream at the time the buffer was created. + /// + /// Thrown if is . + /// Thrown if has been disposed. + /// Thrown if the underlying array that is used can't be accessed. + public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream stream) + { + // Note: the naming inconsistency with 'byte[].AsBuffer' is intentional. This extension method will appear on + // 'MemoryStream', so consistency with method names on 'MemoryStream' is more important. There we already have + // an API called 'GetBuffer,' which returns the underlying array. + + ArgumentNullException.ThrowIfNull(stream); + + // Try to extract the underlying buffer from the provided stream. We can only construct a Windows Runtime + // buffer instance if this succeeds. Otherwise, there's no way to actually get the memory area we need. + if (!stream.TryGetBuffer(out ArraySegment arraySegment)) + { + UnauthorizedAccessException.ThrowInternalBufferAccess(); + } + + Debug.Assert(stream.Length <= int.MaxValue); + Debug.Assert(stream.Capacity <= int.MaxValue); + + return new WindowsRuntimeExternalArrayBuffer(arraySegment.Array!, arraySegment.Offset, (int)stream.Length, stream.Capacity); + } + + /// + /// Creates a new instance backed by the same memory as the specified instance. + /// + /// The to use to share the data memory with the buffer being created. + /// The position of the shared memory region. + /// The maximum size of the shared memory region. + /// A new instance backed by the same memory as . + /// + /// + /// The instance may re-sized in future, which would cause that stream to be backed by a different memory region. + /// In that scenario, the buffer created by this method will remain backed by the memory behind the stream at the time the buffer was created. + /// + /// + /// The created buffer begins at the specified position in the stream, and extends over up to bytes. + /// If the stream has less than bytes after the specified starting position, the created buffer covers + /// only as many bytes as available in the stream. In either case, the and the + /// properties of the created buffer are set accordingly: + /// + /// + /// : number of bytes between + /// and the stream capacity end, but not more than . + /// + /// + /// : number of bytes between and the stream length end, + /// or zero if is beyond stream length end, but not more than . + /// + /// + /// + /// + /// Thrown if is . + /// Thrown if or are less than 0. + /// Thrown if has been disposed. + /// Thrown if the underlying array that is used can't be accessed. + public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream stream, int position, int length) + { + ArgumentNullException.ThrowIfNull(stream); + ArgumentOutOfRangeException.ThrowIfNegative(position); + ArgumentOutOfRangeException.ThrowIfNegative(length); + ArgumentException.ThrowIfStreamPositionBeyondEndOfStream(stream.Length, position); + + // Extract the underlying buffer from the stream (same as above) + if (!stream.TryGetBuffer(out ArraySegment arraySegment)) + { + UnauthorizedAccessException.ThrowInternalBufferAccess(); + } + + int bufferOffset = arraySegment.Offset + position; + int bufferCapacity = Math.Min(length, stream.Capacity - position); + int bufferLength = Math.Max(0, Math.Min(length, (int)stream.Length - position)); + + return new WindowsRuntimeExternalArrayBuffer(arraySegment.Array!, bufferOffset, bufferLength, bufferCapacity); + } + + /// + /// Returns a object that represents the same memory that the specified instance represents. + /// + /// The instance to wrap as a stream. + /// A stream that represents the same memory that the specified instance represents. + /// Thrown if is . + /// Thrown if is not a valid implementation. + /// Thrown if invoking IBufferByteAccess.Buffer on either input buffer fails. + public static unsafe Stream AsStream(this IBuffer source) + { + ArgumentNullException.ThrowIfNull(source); + + // If the buffer is backed by a managed array, create a stream around it + if (TryGetArray(source, out byte[]? array, out int offset)) + { + return new WindowsRuntimeBufferMemoryStream(source, array, offset); + } + + // At this point the buffer must be a native object wrapper, so validate that it is the case + if (TryGetData(source, out byte* data)) + { + return new WindowsRuntimeBufferUnmanagedMemoryStream(source, data); + } + + // The buffer is not one we can wrap in a stream + throw ArgumentException.GetInvalidIBufferInstanceException(); + } + + /// + /// Returns the byte at the specified offset in the specified instance. + /// + /// The instance to get the byte from. + /// The offset of the byte. + /// The byte at the specified offset. + /// Thrown if is . + /// Thrown if is not in a valid range for . + /// Thrown if invoking IBufferByteAccess.Buffer on either input buffer fails. + public static byte GetByte(this IBuffer source, uint byteOffset) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentException.ThrowIfBufferOffsetOutOfRange(byteOffset, source.Length); + + Span span = GetSpanForCapacity(source); + + byte value = span[(int)byteOffset]; + + GC.KeepAlive(source); + + return value; + } + + /// + /// Gets a value for the underlying data in the specified buffer. + /// + /// The input instance. + /// The resulting value. + /// + /// The returned value has a length equal to , not . + /// + private static unsafe Span GetSpanForCapacity(IBuffer buffer) + { + // Check for managed buffers first + if (TryGetManagedSpanForCapacity(buffer, out Span span)) + { + return span; + } + + // Check for native buffers next + if (TryGetData(buffer, out byte* data)) + { + return new(data, checked((int)buffer.Capacity)); + } + + // If we got here, it means we don't recognize the input buffer instance. + // There is nothing we can do, but also this shouldn't really happen. + throw ArgumentException.GetInvalidIBufferInstanceException(); + } + + /// + /// Tries to get a value for the underlying data in the specified buffer, only if backed by a managed array. + /// + /// The input instance. + /// The resulting value, if retrieved. + /// Whether could be retrieved. + /// + /// The returned value has a length equal to , not . + /// + private static bool TryGetManagedSpanForCapacity(IBuffer buffer, out Span span) + { + // If the buffer is backed by a managed array, return it + if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + { + span = externalArrayBuffer.GetSpanForCapacity(); + + return true; + } + + // Same as above for pinned arrays as well + if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) + { + span = pinnedArrayBuffer.GetSpanForCapacity(); + + return true; + } + + span = default; + + return false; + } + + /// + /// Tries to get the underlying data for the specified buffer, only if backed by native memory. + /// + /// The input instance. + /// The underlying data, if retrieved. + /// Whether could be retrieved. + private static unsafe bool TryGetData(IBuffer buffer, out byte* data) + { + // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', just optimized to only check for this + if (buffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) + { + using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); + + fixed (byte** dataPtr = &data) + { + HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), dataPtr); + + RestrictedErrorInfo.ThrowExceptionForHR(hresult); + } + + return true; + } + + data = null; + + return false; + } + + /// + /// Tries to get the underlying array for the specified buffer, only if backed by a managed array. + /// + /// The input instance. + /// The underlying array, if retrieved. + /// The offset in the returned array. + /// Whether could be retrieved. + private static bool TryGetArray(IBuffer buffer, [NotNullWhen(true)] out byte[]? array, out int offset) + { + // If the buffer is backed by a managed array, unwrap it + if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + { + array = externalArrayBuffer.GetArray(out offset); + + return true; + } + + // Same as above for pinned arrays as well + if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) + { + array = pinnedArrayBuffer.GetArray(out offset); + + return true; + } + + array = null; + offset = 0; + + return false; + } +} diff --git a/src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IBuffer.cs b/src/WinRT.Runtime2/Windows.Storage/Streams/IBuffer.cs similarity index 100% rename from src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IBuffer.cs rename to src/WinRT.Runtime2/Windows.Storage/Streams/IBuffer.cs diff --git a/src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IInputStream.cs b/src/WinRT.Runtime2/Windows.Storage/Streams/IInputStream.cs similarity index 100% rename from src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IInputStream.cs rename to src/WinRT.Runtime2/Windows.Storage/Streams/IInputStream.cs diff --git a/src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IOutputStream.cs b/src/WinRT.Runtime2/Windows.Storage/Streams/IOutputStream.cs similarity index 100% rename from src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IOutputStream.cs rename to src/WinRT.Runtime2/Windows.Storage/Streams/IOutputStream.cs diff --git a/src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IRandomAccessStream.cs b/src/WinRT.Runtime2/Windows.Storage/Streams/IRandomAccessStream.cs similarity index 100% rename from src/WinRT.Runtime2/Windows.Storage.Streams/Streams/IRandomAccessStream.cs rename to src/WinRT.Runtime2/Windows.Storage/Streams/IRandomAccessStream.cs diff --git a/src/WinRT.Runtime2/Windows.Storage.Streams/Streams/InputStreamOptions.cs b/src/WinRT.Runtime2/Windows.Storage/Streams/InputStreamOptions.cs similarity index 100% rename from src/WinRT.Runtime2/Windows.Storage.Streams/Streams/InputStreamOptions.cs rename to src/WinRT.Runtime2/Windows.Storage/Streams/InputStreamOptions.cs diff --git a/src/cswinrt/cswinrt.vcxproj b/src/cswinrt/cswinrt.vcxproj index 5cb779ae05..5901eed4d0 100644 --- a/src/cswinrt/cswinrt.vcxproj +++ b/src/cswinrt/cswinrt.vcxproj @@ -76,15 +76,10 @@ - - - - - diff --git a/src/cswinrt/cswinrt.vcxproj.filters b/src/cswinrt/cswinrt.vcxproj.filters index 1b4d681b01..1b832cd1b5 100644 --- a/src/cswinrt/cswinrt.vcxproj.filters +++ b/src/cswinrt/cswinrt.vcxproj.filters @@ -88,21 +88,6 @@ strings\additions\Windows.Storage.Streams - - strings\additions\Windows.Storage.Streams - - - strings\additions\Windows.Storage.Streams - - - strings\additions\Windows.Storage.Streams - - - strings\additions\Windows.Storage.Streams - - - strings\additions\Windows.Storage.Streams - strings\additions\Windows.Storage diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp index 78dbb3aee7..fa2c18e868 100644 --- a/src/cswinrt/main.cpp +++ b/src/cswinrt/main.cpp @@ -314,16 +314,6 @@ Where is one or more of: break; } } - - // Attributes need to be written at the start, so handling this addition separately. - if (ns == "Windows.Storage.Streams" && settings.addition_filter.includes(ns)) - { - w.write(R"( -[assembly: TypeMapAssociation( - typeof(global::System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBuffer), - typeof(global::ABI.System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBuffer))] -)"); - } } currentType = ""; diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/IBufferByteAccess.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/IBufferByteAccess.cs deleted file mode 100644 index a4c1e09163..0000000000 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/IBufferByteAccess.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Windows.Storage.Streams -{ - [WindowsRuntimeMetadata("Windows.Foundation.UniversalApiContract")] - [Guid("905a0fef-bc53-11df-8c49-001e4fc686da")] - internal unsafe interface IBufferByteAccess - { - byte* Buffer { get; } - } -} - -namespace ABI.Windows.Storage.Streams -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct IBufferByteAccessVftbl - { - public delegate* unmanaged[MemberFunction] QueryInterface; - public delegate* unmanaged[MemberFunction] AddRef; - public delegate* unmanaged[MemberFunction] Release; - public delegate* unmanaged[MemberFunction] get_Buffer; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBufferUnsafe( - void* thisPtr, - byte** result) - { - return ((IBufferByteAccessVftbl*)*(void***)thisPtr)->get_Buffer(thisPtr, result); - } - } - - internal static unsafe class IBufferByteAccessImpl - { - [FixedAddressValueType] - private static readonly IBufferByteAccessVftbl Vftbl; - - static IBufferByteAccessImpl() - { - *(IUnknownVftbl*)Unsafe.AsPointer(ref Vftbl) = *(IUnknownVftbl*)IUnknownImpl.Vtable; - Vftbl.get_Buffer = &Do_Abi_get_Buffer; - } - - public static nint Vtable - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (nint)Unsafe.AsPointer(in Vftbl); - } - - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvMemberFunction) })] - private static unsafe int Do_Abi_get_Buffer(void* thisPtr, byte** result) - { - *result = default; - - try - { - *result = ComInterfaceDispatch.GetInstance((ComInterfaceDispatch*)thisPtr).Buffer; - return 0; - } - catch (Exception __exception__) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(__exception__); - } - } - } -} diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/IMarshal.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/IMarshal.cs deleted file mode 100644 index 8bab861e33..0000000000 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/IMarshal.cs +++ /dev/null @@ -1,294 +0,0 @@ -namespace ABI.Windows.Storage.Streams -{ - /// - /// Binding type for the IMarshal interface vtable. - /// - /// - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct IBufferMarshalVftbl - { - public delegate* unmanaged[MemberFunction] QueryInterface; - public delegate* unmanaged[MemberFunction] AddRef; - public delegate* unmanaged[MemberFunction] Release; - public delegate* unmanaged[MemberFunction] GetUnmarshalClass; - public delegate* unmanaged[MemberFunction] GetMarshalSizeMax; - public delegate* unmanaged[MemberFunction] MarshalInterface; - public delegate* unmanaged[MemberFunction] UnmarshalInterface; - public delegate* unmanaged[MemberFunction] ReleaseMarshalData; - public delegate* unmanaged[MemberFunction] DisconnectObject; - - /// - /// Retrieves the CLSID of the unmarshaling code. - /// - /// The target COM object. - /// A reference to the identifier of the interface to be marshaled. - /// A pointer to the interface to be marshaled (can be ). - /// The destination context where the specified interface is to be unmarshaled. - /// This parameter is reserved and must be . - /// Indicates whether the data to be marshaled is to be transmitted back to the client process or written to a global table. - /// A pointer that receives the CLSID to be used to create a proxy in the client process. - /// The HRESULT for the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetUnmarshalClassUnsafe( - void* thisPtr, - Guid* riid, - void* pv, - uint dwDestContext, - void* pvDestContext, - uint mshlflags, - Guid* pCid) - { - return ((IBufferMarshalVftbl*)*(void***)thisPtr)->GetUnmarshalClass(thisPtr, riid, pv, dwDestContext, pvDestContext, mshlflags, pCid); - } - - /// - /// Retrieves the maximum size of the buffer that will be needed during marshaling. - /// - /// The target COM object. - /// A reference to the identifier of the interface to be marshaled. - /// A pointer to the interface to be marshaled (can be ). - /// The destination context where the specified interface is to be unmarshaled. - /// This parameter is reserved and must be . - /// Indicates whether the data to be marshaled is to be transmitted back to the client process or written to a global table. - /// A pointer to a variable that receives the maximum size of the buffer. - /// The HRESULT for the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetMarshalSizeMaxUnsafe( - void* thisPtr, - Guid* riid, - void* pv, - uint dwDestContext, - void* pvDestContext, - uint mshlflags, - uint* pSize) - { - return ((IBufferMarshalVftbl*)*(void***)thisPtr)->GetMarshalSizeMax(thisPtr, riid, pv, dwDestContext, pvDestContext, mshlflags, pSize); - } - - /// - /// Marshals an interface pointer. - /// - /// The target COM object. - /// A pointer to the stream to be used during marshaling. - /// A reference to the identifier of the interface to be marshaled. - /// A pointer to the interface to be marshaled (can be ). - /// The destination context where the specified interface is to be unmarshaled. - /// This parameter is reserved and must be . - /// Indicates whether the data to be marshaled is to be transmitted back to the client process or written to a global table. - /// The HRESULT for the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int MarshalInterfaceUnsafe( - void* thisPtr, - void* pStm, - Guid* riid, - void* pv, - uint dwDestContext, - void* pvDestContext, - uint mshlflags) - { - return ((IBufferMarshalVftbl*)*(void***)thisPtr)->MarshalInterface(thisPtr, pStm, riid, pv, dwDestContext, pvDestContext, mshlflags); - } - - /// - /// Unmarshals an interface pointer. - /// - /// The target COM object. - /// A pointer to the stream from which the interface pointer is to be unmarshaled. - /// A reference to the identifier of the interface to be unmarshaled. - /// The address of pointer variable that receives the interface pointer. - /// The HRESULT for the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int UnmarshalInterfaceUnsafe( - void* thisPtr, - void* pStm, - Guid* riid, - void** ppv) - { - return ((IBufferMarshalVftbl*)*(void***)thisPtr)->UnmarshalInterface(thisPtr, pStm, riid, ppv); - } - - /// - /// Destroys a marshaled data packet. - /// - /// The target COM object. - /// A pointer to a stream that contains the data packet to be destroyed. - /// The HRESULT for the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ReleaseMarshalDataUnsafe(void* thisPtr, void* pStm) - { - return ((IBufferMarshalVftbl*)*(void***)thisPtr)->ReleaseMarshalData(thisPtr, pStm); - } - - /// - /// Releases all connections to an object. - /// - /// The target COM object. - /// This parameter is reserved and must be 0. - /// The HRESULT for the operation. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int DisconnectObjectUnsafe(void* thisPtr, uint dwReserved) - { - return ((IBufferMarshalVftbl*)*(void***)thisPtr)->DisconnectObject(thisPtr, dwReserved); - } - } - - internal static unsafe class IBufferMarshalImpl - { - /// - /// The value for the managed IMarshal implementation using RoGetBufferMarshaler as its implementation. - /// - [FixedAddressValueType] - private static readonly IBufferMarshalVftbl Vftbl; - - [ThreadStatic] - private static void* t_winRtMarshalProxy = null; - - /// - /// Initializes . - /// - static IBufferMarshalImpl() - { - *(IUnknownVftbl*)Unsafe.AsPointer(ref Vftbl) = *(IUnknownVftbl*)IUnknownImpl.Vtable; - - Vftbl.GetUnmarshalClass = &GetUnmarshalClass; - Vftbl.GetMarshalSizeMax = &GetMarshalSizeMax; - Vftbl.MarshalInterface = &MarshalInterface; - Vftbl.UnmarshalInterface = &UnmarshalInterface; - Vftbl.ReleaseMarshalData = &ReleaseMarshalData; - Vftbl.DisconnectObject = &DisconnectObject; - } - - [DllImport("api-ms-win-core-winrt-robuffer-l1-1-0.dll")] - private static extern unsafe int RoGetBufferMarshaler(void** bufferMarshalerPtr); - - private const string WinTypesDLL = "WinTypes.dll"; - - private static unsafe void EnsureHasMarshalProxy() - { - if (t_winRtMarshalProxy != null) - return; - - try - { - void* proxyPtr = null; - RestrictedErrorInfo.ThrowExceptionForHR(RoGetBufferMarshaler(&proxyPtr)); - if (proxyPtr == null) - { - throw new NullReferenceException(string.Format("{0} ({1}!RoGetBufferMarshaler)", global::Windows.Storage.Streams.SR.WinRtCOM_Error, WinTypesDLL)); - } - t_winRtMarshalProxy = proxyPtr; - } - catch (DllNotFoundException ex) - { - throw new NotImplementedException(string.Format(global::Windows.Storage.Streams.SR.NotImplemented_NativeRoutineNotFound, - string.Format("{0}!RoGetBufferMarshaler", WinTypesDLL)), - ex); - } - } - - /// - /// Gets a pointer to the managed IMarshal implementation using RoGetBufferMarshaler. - /// - public static nint Vtable - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (nint)Unsafe.AsPointer(in Vftbl); - } - - /// - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int GetUnmarshalClass(void* thisPtr, Guid* riid, void* pv, uint dwDestContext, void* pvDestContext, uint mshlflags, Guid* pCid) - { - *pCid = default; - - try - { - EnsureHasMarshalProxy(); - return IBufferMarshalVftbl.GetUnmarshalClassUnsafe(t_winRtMarshalProxy, riid, pv, dwDestContext, pvDestContext, mshlflags, pCid); - } - catch (Exception ex) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); - } - } - - /// - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int GetMarshalSizeMax(void* thisPtr, Guid* riid, void* pv, uint dwDestContext, void* pvDestContext, uint mshlflags, uint* pSize) - { - *pSize = 0; - - try - { - EnsureHasMarshalProxy(); - return IBufferMarshalVftbl.GetMarshalSizeMaxUnsafe(t_winRtMarshalProxy, riid, pv, dwDestContext, pvDestContext, mshlflags, pSize); - } - catch (Exception ex) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); - } - } - - /// - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int MarshalInterface(void* thisPtr, void* pStm, Guid* riid, void* pv, uint dwDestContext, void* pvDestContext, uint mshlflags) - { - try - { - EnsureHasMarshalProxy(); - return IBufferMarshalVftbl.MarshalInterfaceUnsafe(t_winRtMarshalProxy, pStm, riid, pv, dwDestContext, pvDestContext, mshlflags); - } - catch (Exception ex) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); - } - } - - /// - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int UnmarshalInterface(void* thisPtr, void* pStm, Guid* riid, void** ppv) - { - *ppv = null; - - try - { - EnsureHasMarshalProxy(); - return IBufferMarshalVftbl.UnmarshalInterfaceUnsafe(t_winRtMarshalProxy, pStm, riid, ppv); - } - catch (Exception ex) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); - } - } - - /// - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int ReleaseMarshalData(void* thisPtr, void* pStm) - { - try - { - EnsureHasMarshalProxy(); - return IBufferMarshalVftbl.ReleaseMarshalDataUnsafe(t_winRtMarshalProxy, pStm); - } - catch (Exception ex) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); - } - } - - /// - [UnmanagedCallersOnly(CallConvs = [typeof(CallConvMemberFunction)])] - private static int DisconnectObject(void* thisPtr, uint dwReserved) - { - try - { - EnsureHasMarshalProxy(); - return IBufferMarshalVftbl.DisconnectObjectUnsafe(t_winRtMarshalProxy, dwReserved); - } - catch (Exception ex) - { - return RestrictedErrorInfoExceptionMarshaller.ConvertToUnmanaged(ex); - } - } - } -} \ No newline at end of file diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/IMemoryBufferByteAccess.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/IMemoryBufferByteAccess.cs deleted file mode 100644 index f5a0041c47..0000000000 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/IMemoryBufferByteAccess.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace ABI.Windows.Storage.Streams -{ - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct IMemoryBufferByteAccessVftbl - { - public delegate* unmanaged[MemberFunction] QueryInterface; - public delegate* unmanaged[MemberFunction] AddRef; - public delegate* unmanaged[MemberFunction] Release; - public delegate* unmanaged[MemberFunction] GetBuffer; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBufferUnsafe( - void* thisPtr, - byte** result, - uint* capacity) - { - return ((IMemoryBufferByteAccessVftbl*)*(void***)thisPtr)->GetBuffer(thisPtr, result, capacity); - } - } -} diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs index 7e283bccce..a2201d007c 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs @@ -6,13 +6,12 @@ namespace Windows.Storage.Streams { using global::System.Diagnostics; - using global::System.IO; - + using global::System.IO; using global::System.Runtime.InteropServices; using global::System.Threading.Tasks; using global::System.Threading; - using global::System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; + using Windows.Storage.Buffers; /// Depending on the concrete type of the stream managed by a NetFxToWinRtStreamAdapter, /// we want the ReadAsync / WriteAsync / FlushAsync / etc. operation to be implemented @@ -82,7 +81,7 @@ internal static IAsyncOperationWithProgress ReadAsync_AbstractStr // Note: the allocation costs we are paying for the new buffer are unavoidable anyway, as we would need to create // an array to read into either way. - IBuffer dataBuffer = buffer as WindowsRuntimeBuffer; + IBuffer dataBuffer = null; // buffer as WindowsRuntimeBuffer; // TODO if (dataBuffer == null) dataBuffer = WindowsRuntimeBuffer.Create((int)Math.Min((uint)int.MaxValue, buffer.Capacity)); @@ -94,10 +93,10 @@ internal static IAsyncOperationWithProgress ReadAsync_AbstractStr dataBuffer.Length = 0; // Get the buffer backing array: - byte[] data; - int offset; - bool managedBufferAssert = dataBuffer.TryGetUnderlyingData(out data, out offset); - Debug.Assert(managedBufferAssert); + byte[] data = []; // TODO + int offset = 0; // TODO + //bool managedBufferAssert = dataBuffer.TryGetUnderlyingData(out data, out offset); TODO + //Debug.Assert(managedBufferAssert); // Init tracking values: bool done = cancelToken.IsCancellationRequested; @@ -166,11 +165,12 @@ internal static IAsyncOperationWithProgress WriteAsync_AbstractStrea // Choose the optimal writing strategy for the kind of buffer supplied: Func, Task> writeOperation; - byte[] data; - int offset; + byte[] data = []; // TODO + int offset = 0; // TODO // If buffer is backed by a managed array: - if (buffer.TryGetUnderlyingData(out data, out offset)) + //if (buffer.TryGetUnderlyingData(out data, out offset)) TODO + if (false) { writeOperation = async (cancelToken, progressListener) => { diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs index e8356c67de..62f90eb170 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs @@ -7,10 +7,10 @@ namespace Windows.Storage.Streams { using global::System.Diagnostics; using global::System.IO; - using global::System.Runtime.ExceptionServices; - + using global::System.Runtime.ExceptionServices; using global::System.Runtime.InteropServices; - using global::System.Runtime.InteropServices.WindowsRuntime; + using Windows.Storage.Buffers; + internal static class WinRtIOHelper { internal const int DefaultIOBufferSize = 0x3000; // = 12 KBytes = 12288 Bytes diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs index 0cd3d46f6a..4ae9c8a65b 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs @@ -6,14 +6,15 @@ namespace Windows.Storage.Streams { using global::System.Diagnostics; + using global::System.Diagnostics.CodeAnalysis; using global::System.Diagnostics.Contracts; using global::System.IO; using global::System.Runtime.InteropServices; - using global::System.Runtime.InteropServices.WindowsRuntime; using global::System.Threading.Tasks; using global::System.Threading; using Windows.Foundation; - using global::System.Diagnostics.CodeAnalysis; + using Windows.Storage.Buffers; + /// /// A Stream used to wrap a Windows Runtime stream to expose it as a managed steam. /// diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBuffer.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBuffer.cs deleted file mode 100644 index 1a522bcb2b..0000000000 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBuffer.cs +++ /dev/null @@ -1,288 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.Runtime.InteropServices.WindowsRuntime -{ - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - using System.Security; - using System.Threading; - using global::Windows.Foundation; - using global::Windows.Storage.Streams; - - /// - /// Contains an implementation of the WinRT IBuffer interface that conforms to all requirements on classes that implement that interface, - /// such as implementing additional interfaces. - /// - [WindowsRuntimeManagedOnlyType] - public sealed class WindowsRuntimeBuffer : IBuffer, IBufferByteAccess - { - #region Constants - - private const int E_BOUNDS = unchecked((int)0x8000000B); - - #endregion Constants - - - #region Static factory methods - - public static IBuffer Create(int capacity) - { - if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - - return new WindowsRuntimeBuffer(capacity); - } - - - public static IBuffer Create(byte[] data, int offset, int length, int capacity) - { - if (data == null) throw new ArgumentNullException(nameof(data)); - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); - if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - if (data.Length - offset < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (data.Length - offset < capacity) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (capacity < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientBufferCapacity); - - byte[] underlyingData = new byte[capacity]; - Array.Copy(data, offset, underlyingData, 0, length); - return new WindowsRuntimeBuffer(underlyingData, 0, length, capacity); - } - - #endregion Static factory methods - - #region Fields - - private readonly byte[] _data; - private readonly int _dataStartOffs = 0; - private int _usefulDataLength = 0; - private readonly int _maxDataCapacity = 0; - private GCHandle _pinHandle; - - // Pointer to data[dataStartOffs] when data is pinned: - private IntPtr _dataPtr = IntPtr.Zero; - - #endregion Fields - - - #region Constructors - - internal WindowsRuntimeBuffer(int capacity) - { - if (capacity < 0) - throw new ArgumentOutOfRangeException(nameof(capacity)); - - _data = new byte[capacity]; - _dataStartOffs = 0; - _usefulDataLength = 0; - _maxDataCapacity = capacity; - _dataPtr = IntPtr.Zero; - } - - - internal WindowsRuntimeBuffer(byte[] data, int offset, int length, int capacity) - { - if (data == null) throw new ArgumentNullException(nameof(data)); - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); - if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - if (data.Length - offset < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (data.Length - offset < capacity) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (capacity < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientBufferCapacity); - - _data = data; - _dataStartOffs = offset; - _usefulDataLength = length; - _maxDataCapacity = capacity; - _dataPtr = IntPtr.Zero; - } - - #endregion Constructors - - - #region Helpers - - internal void GetUnderlyingData(out byte[] underlyingDataArray, out int underlyingDataArrayStartOffset) - { - underlyingDataArray = _data; - underlyingDataArrayStartOffset = _dataStartOffs; - } - - private unsafe byte* PinUnderlyingData() - { - GCHandle gcHandle = default(GCHandle); - bool ptrWasStored = false; - IntPtr buffPtr; - - try { } - finally - { - try - { - // Pin the data array: - gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned); - buffPtr = gcHandle.AddrOfPinnedObject() + _dataStartOffs; - - // Store the pin IFF it has not been assigned: - ptrWasStored = (Interlocked.CompareExchange(ref _dataPtr, buffPtr, IntPtr.Zero) == IntPtr.Zero); - } - finally - { - if (!ptrWasStored) - { - // There is a race with another thread also trying to create a pin and they were first - // in assigning to data pin. That's ok, just give it up. - // Unpin again (the pin from the other thread remains): - gcHandle.Free(); - } - else - { - if (_pinHandle.IsAllocated) - _pinHandle.Free(); - - // Make sure we keep track of the handle - _pinHandle = gcHandle; - } - } - } - - // Ok, now all is good: - return (byte*)buffPtr; - } - - ~WindowsRuntimeBuffer() - { - if (_pinHandle.IsAllocated) - _pinHandle.Free(); - } - - #endregion Helpers - - - #region Implementation of Windows.Foundation.IBuffer - - uint IBuffer.Capacity - { - get { return unchecked((uint)_maxDataCapacity); } - } - - - uint IBuffer.Length - { - get - { - return unchecked((uint)_usefulDataLength); - } - - set - { - if (value > ((IBuffer)this).Capacity) - { - ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException(nameof(value), global::Windows.Storage.Streams.SR.Argument_BufferLengthExceedsCapacity); - ex.HResult = E_BOUNDS; - throw ex; - } - - // Capacity is ensured to not exceed Int32.MaxValue, so Length is within this limit and this cast is safe: - Debug.Assert(((IBuffer)this).Capacity <= int.MaxValue); - _usefulDataLength = unchecked((int)value); - } - } - - #endregion Implementation of Windows.Foundation.IBuffer - - - #region Implementation of IBufferByteAccess - - unsafe byte* IBufferByteAccess.Buffer - { - get - { - // Get pin handle: - IntPtr buffPtr = Volatile.Read(ref _dataPtr); - - // If we are already pinned, return the pointer: - if (buffPtr != IntPtr.Zero) - return (byte*)buffPtr; - - // Otherwise pin it. - return PinUnderlyingData(); - } - } - - #endregion Implementation of IBufferByteAccess - } // class WindowsRuntimeBuffer -} // namespace - -#if !CSWINRT_REFERENCE_PROJECTION -namespace ABI.System.Runtime.InteropServices.WindowsRuntime -{ - [WindowsRuntimeClassName("Windows.Storage.Streams.IBuffer")] - [WindowsRuntimeBufferComWrappersMarshaller] - file static class WindowsRuntimeBuffer; - - file struct WindowsRuntimeBufferInterfaceEntries - { - public ComInterfaceEntry IBuffer; - public ComInterfaceEntry IBufferByteAccess; - public ComInterfaceEntry IStringable; - public ComInterfaceEntry IWeakReferenceSource; - public ComInterfaceEntry IMarshal; - public ComInterfaceEntry IAgileObject; - public ComInterfaceEntry IInspectable; - public ComInterfaceEntry IUnknown; - } - - file static class WindowsRuntimeBufferInterfaceEntriesImpl - { - [FixedAddressValueType] - public static readonly WindowsRuntimeBufferInterfaceEntries Entries; - - /// - /// Initializes . - /// - static WindowsRuntimeBufferInterfaceEntriesImpl() - { - Entries.IBuffer.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_Windows_Storage_Streams_IBuffer; - Entries.IBuffer.Vtable = global::ABI.Windows.Storage.Streams.IBufferImpl.Vtable; - Entries.IBufferByteAccess.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IBufferByteAccess; - Entries.IBufferByteAccess.Vtable = global::ABI.Windows.Storage.Streams.IBufferByteAccessImpl.Vtable; - Entries.IStringable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IStringable; - Entries.IStringable.Vtable = global::WindowsRuntime.InteropServices.IStringableImpl.Vtable; - Entries.IWeakReferenceSource.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IWeakReferenceSource; - Entries.IWeakReferenceSource.Vtable = global::WindowsRuntime.InteropServices.IWeakReferenceSourceImpl.Vtable; - Entries.IMarshal.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IMarshal; - // This uses a custom IMarshal implmentation for buffers. - Entries.IMarshal.Vtable = global::ABI.Windows.Storage.Streams.IBufferMarshalImpl.Vtable; - Entries.IAgileObject.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IAgileObject; - Entries.IAgileObject.Vtable = global::WindowsRuntime.InteropServices.IAgileObjectImpl.Vtable; - Entries.IInspectable.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IInspectable; - Entries.IInspectable.Vtable = global::WindowsRuntime.InteropServices.IInspectableImpl.Vtable; - Entries.IUnknown.IID = global::WindowsRuntime.InteropServices.WellKnownInterfaceIIDs.IID_IUnknown; - Entries.IUnknown.Vtable = global::WindowsRuntime.InteropServices.IUnknownImpl.Vtable; - } - } - - file sealed unsafe class WindowsRuntimeBufferComWrappersMarshallerAttribute : WindowsRuntimeComWrappersMarshallerAttribute - { - /// - public override void* GetOrCreateComInterfaceForObject(object value) - { - return WindowsRuntimeComWrappersMarshal.GetOrCreateComInterfaceForObject(value, CreateComInterfaceFlags.TrackerSupport); - } - - /// - public override ComInterfaceEntry* ComputeVtables(out int count) - { - count = sizeof(WindowsRuntimeBufferInterfaceEntries) / sizeof(ComInterfaceEntry); - - return (ComInterfaceEntry*)Unsafe.AsPointer(in WindowsRuntimeBufferInterfaceEntriesImpl.Entries); - } - } -} -#endif - -// WindowsRuntimeBuffer.cs diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs deleted file mode 100644 index 45433968d5..0000000000 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs +++ /dev/null @@ -1,628 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - - -namespace System.Runtime.InteropServices.WindowsRuntime -{ - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using global::Windows.Foundation; - using global::Windows.Storage.Streams; - using global::WindowsRuntime.InteropServices; - - /// - /// Contains extension methods that expose operations on WinRT Windows.Foundation.IBuffer. - /// - public static class WindowsRuntimeBufferExtensions - { -#region (Byte []).AsBuffer extensions - - public static IBuffer AsBuffer(this byte[] source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - return AsBuffer(source, 0, source.Length, source.Length); - } - - - public static IBuffer AsBuffer(this byte[] source, int offset, int length) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); - if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - if (source.Length - offset < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - - return AsBuffer(source, offset, length, length); - } - - - public static IBuffer AsBuffer(this byte[] source, int offset, int length, int capacity) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); - if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); - if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - if (source.Length - offset < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (source.Length - offset < capacity) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientArrayElementsAfterOffset); - if (capacity < length) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_InsufficientBufferCapacity); - - return new WindowsRuntimeBuffer(source, offset, length, capacity); - } - -#endregion (Byte []).AsBuffer extensions - - -#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) - - -#region (IBuffer).ToArray extensions for copying to a new (Byte []) - - public static byte[] ToArray(this IBuffer source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - return ToArray(source, 0, checked((int)source.Length)); - } - - - public static byte[] ToArray(this IBuffer source, uint sourceIndex, 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 (count == 0) - return Array.Empty(); - - byte[] destination = new byte[count]; - source.CopyTo(sourceIndex, destination, 0, count); - return destination; - } - -#endregion (IBuffer).ToArray extensions for copying to a new (Byte []) - - -#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).Slice(0, (int)count); - 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).Slice(0, (int)count); - Span destSpan = destination.TryGetUnderlyingData(out byte[] destDataArr, out int destOffset) ? destDataArr.AsSpan(destOffset + (int)destinationIndex) : destination.GetSpanForCapacityUnsafe(destinationIndex).Slice(0, (int)count); - - 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) - - -#region Access to underlying array optimised for IBuffers backed by managed arrays (to avoid pinning) - - /// - /// If the specified IBuffer is backed by a managed array, this method will return true and - /// set underlyingDataArray to refer to that array - /// and underlyingDataArrayStartOffset to the value at which the buffer data begins in that array. - /// If the specified IBuffer is not backed by a managed array, this method will return false. - /// This method is required by managed APIs that wish to use the buffer's data with other managed APIs that use - /// arrays without a need for a memory copy. - /// - /// An IBuffer. - /// Will be set to the data array backing buffer or to null. - /// Will be set to the start offset of the buffer data in the backing array - /// or to -1. - /// Whether the IBuffer is backed by a managed byte array. - internal static bool TryGetUnderlyingData(this IBuffer buffer, out byte[] underlyingDataArray, out int underlyingDataArrayStartOffset) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - WindowsRuntimeBuffer winRtBuffer = buffer as WindowsRuntimeBuffer; - if (winRtBuffer == null) - { - underlyingDataArray = null; - underlyingDataArrayStartOffset = -1; - return false; - } - - winRtBuffer.GetUnderlyingData(out underlyingDataArray, out underlyingDataArrayStartOffset); - return true; - } - - - /// - /// Checks if the underlying memory backing two IBuffer instances is actually the same memory. - /// When applied to IBuffer instances backed by managed arrays this method is preferable to a naive comparison - /// (such as ((IBufferByteAccess) buffer).Buffer == ((IBufferByteAccess) otherBuffer).Buffer) because it avoids - /// pinning the backing array which would be necessary if a direct memory pointer was obtained. - /// - /// An IBuffer instance. - /// An IBuffer instance or null. - /// true if the underlying Buffer memory pointer is the same for both specified - /// IBuffer instances (i.e. if they are backed by the same memory); false otherwise. - public static unsafe bool IsSameData(this IBuffer buffer, IBuffer otherBuffer) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - if (otherBuffer == null) - return false; - - if (buffer == otherBuffer) - return true; - - byte[] thisDataArr, otherDataArr; - int thisDataOffs, otherDataOffs; - - bool thisIsManaged = buffer.TryGetUnderlyingData(out thisDataArr, out thisDataOffs); - bool otherIsManaged = otherBuffer.TryGetUnderlyingData(out otherDataArr, out otherDataOffs); - - if (thisIsManaged != otherIsManaged) - return false; - - if (thisIsManaged) - return (thisDataArr == otherDataArr) && (thisDataOffs == otherDataOffs); - - if (!WindowsRuntimeBufferMarshal.TryGetDataUnsafe(buffer, out byte* thisBuff) || - !WindowsRuntimeBufferMarshal.TryGetDataUnsafe(otherBuffer, out byte* otherBuff)) - { - return false; - } - - return thisBuff == otherBuff; - } - -#endregion Access to underlying array optimised for IBuffers backed by managed arrays (to avoid pinning) - - -#region Extensions for co-operation with memory streams (share mem stream data; expose data as managed/unmanaged mem stream) - /// - /// Creates a new IBuffer instance backed by the same memory as is backing the specified MemoryStream. - /// The MemoryStream may re-sized in future, as a result the stream will be backed by a different memory region. - /// In such case, the buffer created by this method will remain backed by the memory behind the stream at the time the buffer was created.
- /// This method can throw an ObjectDisposedException if the specified stream is closed.
- /// This method can throw an UnauthorizedAccessException if the specified stream cannot expose its underlying memory buffer. - ///
- /// A memory stream to share the data memory with the buffer being created. - /// A new IBuffer backed by the same memory as this specified stream. - // The naming inconsistency with (Byte []).AsBuffer is intentional: as this extension method will appear on - // MemoryStream, consistency with method names on MemoryStream is more important. There we already have an API - // called GetBuffer which returns the underlying array. - public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream underlyingStream) - { - if (underlyingStream == null) - throw new ArgumentNullException(nameof(underlyingStream)); - - ArraySegment streamData; - if (!underlyingStream.TryGetBuffer(out streamData)) - { - throw new UnauthorizedAccessException(global::Windows.Storage.Streams.SR.UnauthorizedAccess_InternalBuffer); - } - return new WindowsRuntimeBuffer(streamData.Array!, (int)streamData.Offset, (int)underlyingStream.Length, underlyingStream.Capacity); - } - - - /// - /// Creates a new IBuffer instance backed by the same memory as is backing the specified MemoryStream. - /// The MemoryStream may re-sized in future, as a result the stream will be backed by a different memory region. - /// In such case buffer created by this method will remain backed by the memory behind the stream at the time the buffer was created.
- /// This method can throw an ObjectDisposedException if the specified stream is closed.
- /// This method can throw an UnauthorizedAccessException if the specified stream cannot expose its underlying memory buffer. - /// The created buffer begins at position positionInStream in the stream and extends over up to length bytes. - /// If the stream has less than length bytes after the specified starting position, the created buffer covers only as many - /// bytes as available in the stream. In either case, the Length and the Capacity properties of the created - /// buffer are set accordingly: Capacity - number of bytes between positionInStream and the stream capacity end, - /// but not more than length; Length - number of bytes between positionInStream and the stream - /// length end, or zero if positionInStream is beyond stream length end, but not more than length. - ///
- /// A memory stream to share the data memory with the buffer being created. - /// The position of the shared memory region. - /// The maximum size of the shared memory region. - /// A new IBuffer backed by the same memory as this specified stream. - public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream underlyingStream, int positionInStream, int length) - { - // The naming inconsistency with (Byte []).AsBuffer is intentional: as this extension method will appear on - // MemoryStream, consistency with method names on MemoryStream is more important. There we already have an API - // called GetBuffer which returns the underlying array. - - if (underlyingStream == null) - throw new ArgumentNullException(nameof(underlyingStream)); - - if (positionInStream < 0) - throw new ArgumentOutOfRangeException(nameof(positionInStream)); - - if (length < 0) - throw new ArgumentOutOfRangeException(nameof(length)); - - if (underlyingStream.Length < positionInStream) - throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_StreamPositionBeyondEOS); - - ArraySegment streamData; - - if (!underlyingStream.TryGetBuffer(out streamData)) - { - throw new UnauthorizedAccessException(global::Windows.Storage.Streams.SR.UnauthorizedAccess_InternalBuffer); - } - - int originInStream = streamData.Offset; - int buffCapacity = Math.Min(length, underlyingStream.Capacity - positionInStream); - int buffLength = Math.Max(0, Math.Min(length, ((int)underlyingStream.Length) - positionInStream)); - return new WindowsRuntimeBuffer(streamData.Array!, originInStream + positionInStream, buffLength, buffCapacity); - } - - - public static unsafe Stream AsStream(this IBuffer source) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - - byte[] dataArr; - int dataOffs; - if (source.TryGetUnderlyingData(out dataArr, out dataOffs)) - { - Debug.Assert(source.Capacity < int.MaxValue); - return new WindowsRuntimeBufferMemoryStream(source, dataArr, dataOffs); - } - - if (!WindowsRuntimeBufferMarshal.TryGetDataUnsafe(source, out byte* sourceBuff)) - { - throw new InvalidCastException(); - } - - return new WindowsRuntimeBufferUnmanagedMemoryStream(source, sourceBuff); - } - -#endregion Extensions for co-operation with memory streams (share mem stream data; expose data as managed/unmanaged mem stream) - - -#region Extensions for direct by-offset access to buffer data elements - - public static byte GetByte(this IBuffer source, uint byteOffset) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (source.Length <= byteOffset) throw new ArgumentException("The specified buffer offset is not within the buffer length."); - - byte[] srcDataArr; - int srcDataOffs; - if (source.TryGetUnderlyingData(out srcDataArr, out srcDataOffs)) - { - return srcDataArr[srcDataOffs + byteOffset]; - } - - 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 - - - #region Private plumbing - - private sealed class WindowsRuntimeBufferMemoryStream : MemoryStream - { - private readonly IBuffer _sourceBuffer; - - internal WindowsRuntimeBufferMemoryStream(IBuffer sourceBuffer, byte[] dataArr, int dataOffs) - : base(dataArr, dataOffs, (int)sourceBuffer.Capacity, writable: true) - { - _sourceBuffer = sourceBuffer; - - SetLength((long)sourceBuffer.Length); - } - - public override void SetLength(long value) - { - base.SetLength(value); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override void Write(byte[] buffer, int offset, int count) - { - base.Write(buffer, offset, count); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override void Write(ReadOnlySpan buffer) - { - base.Write(buffer); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await base.WriteAsync(buffer, offset, count, cancellationToken); - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - await base.WriteAsync(buffer, cancellationToken); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override void WriteByte(byte value) - { - base.WriteByte(value); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - } // class WindowsRuntimeBufferMemoryStream - - private sealed class WindowsRuntimeBufferUnmanagedMemoryStream : UnmanagedMemoryStream - { - // We need this class because if we construct an UnmanagedMemoryStream on an IBuffer backed by native memory, - // we must keep around a reference to the IBuffer from which we got the memory pointer. Otherwise the ref count - // of the underlying COM object may drop to zero and the memory may get freed. - - private readonly IBuffer _sourceBuffer; - - internal unsafe WindowsRuntimeBufferUnmanagedMemoryStream(IBuffer sourceBuffer, byte* dataPtr) - - : base(dataPtr, (long)sourceBuffer.Length, (long)sourceBuffer.Capacity, FileAccess.ReadWrite) - { - _sourceBuffer = sourceBuffer; - } - - public override void SetLength(long value) - { - base.SetLength(value); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override void Write(byte[] buffer, int offset, int count) - { - base.Write(buffer, offset, count); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override void Write(ReadOnlySpan buffer) - { - base.Write(buffer); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await base.WriteAsync(buffer, offset, count, cancellationToken); - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - await base.WriteAsync(buffer, cancellationToken); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - - public override void WriteByte(byte value) - { - base.WriteByte(value); - - // Length is limited by Capacity which should be a valid value. - // Therefore this cast is safe. - _sourceBuffer.Length = (uint)Length; - } - } // class WindowsRuntimeBufferUnmanagedMemoryStream - - private static unsafe Span GetSpanForCapacityUnsafe(this IBuffer buffer, uint offset) - { - Debug.Assert(0 <= offset); - Debug.Assert(offset < buffer.Capacity); - - if (!WindowsRuntimeBufferMarshal.TryGetDataUnsafe(buffer, out byte* buffPtr)) - { - throw new InvalidCastException(); - } - - var span = new Span(buffPtr + offset, (int)(buffer.Capacity - offset)); - GC.KeepAlive(buffer); - return span; - } -#endregion Private plumbing - } // class WindowsRuntimeBufferExtensions -} // namespace - -// WindowsRuntimeBufferExtensions.cs