From 392792132b4d024dad34f73f15299525986c0b79 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 19 Feb 2026 17:30:11 -0800 Subject: [PATCH 01/31] Convert Buffer property to Buffer() method Change WindowsRuntimePinnedArrayBuffer.Buffer from an internal property getter to a public unsafe Buffer() method (inlined) and update call sites. Also make GetArraySegment public, add an XML doc and AggressiveInlining, and remove a redundant Debug.Assert for Capacity. Update ABI and WindowsRuntimeBufferMarshal usages to call Buffer(). These changes expose the buffer access method and unify callers to the new signature. --- .../Buffers/WindowsRuntimePinnedArrayBuffer.cs | 2 +- .../Buffers/WindowsRuntimePinnedArrayBuffer.cs | 9 +++++---- .../InteropServices/WindowsRuntimeBufferMarshal.cs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index aef30f533..46dfaab91 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/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index e5017780f..8b694c5c2 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -97,18 +97,19 @@ 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 an value for the underlying data in this buffer. /// /// The resulting value. - internal ArraySegment GetArraySegment() + public ArraySegment GetArraySegment() { return new(_pinnedData, _offset, _length); } diff --git a/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs b/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs index 8cac8ccbe..de5fe934f 100644 --- a/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs +++ b/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs @@ -51,7 +51,7 @@ public static unsafe bool TryGetDataUnsafe([NotNullWhen(true)] IBuffer? buffer, // Also handle a managed instance of the pinned array buffer type from 'WinRT.Runtime.dll' if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) { - data = pinnedArrayBuffer.Buffer; + data = pinnedArrayBuffer.Buffer(); return true; } From 4b377da5349ed58332bb7cc0c4e27b4e01e9b162 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 19 Feb 2026 17:30:19 -0800 Subject: [PATCH 02/31] Add WindowsRuntimeExternalArrayBuffer for external arrays Introduce WindowsRuntimeExternalArrayBuffer, an internal sealed implementation of IBuffer that wraps an external byte[] with support for offset, length and capacity. Provides Buffer() and GetArraySegment() APIs and a Length setter that enforces capacity (throws E_BOUNDS). Implements a pinning strategy using PinnedGCHandle and caches the pinned data pointer with Interlocked.CompareExchange to be thread-safe, and disposes the pinned handle in a finalizer. Includes debug assertions for constructor parameters and is marked as a managed-only WinRT type. --- .../WindowsRuntimeExternalArrayBuffer.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs new file mode 100644 index 000000000..34a595efe --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -0,0 +1,177 @@ +// 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 + { + if (value > Capacity) + { + ArgumentOutOfRangeException ex = new(nameof(value), global::Windows.Foundation.SR.Argument_BufferLengthExceedsCapacity) + { + HResult = WellKnownErrorCodes.E_BOUNDS + }; + + throw ex; + } + + _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 ArraySegment GetArraySegment() + { + return new(_data, _offset, _length); + } + + /// + /// 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, than 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; + } +} From b563d1388c6ea6b8db9b3ff059c78667561850fa Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 19 Feb 2026 22:36:08 -0800 Subject: [PATCH 03/31] Add WindowsRuntimeExternalArrayBuffer marshaller Introduce ABI support and a custom marshaller for WindowsRuntimeExternalArrayBuffer. Adds TypeMapAssociation, a file-scoped ABI type, and a fixed set of ComInterfaceEntry vtables (IBuffer, IBufferByteAccess, IStringable, IWeakReferenceSource, IMarshal, IAgileObject, IInspectable, IUnknown). Implements a WindowsRuntimeComWrappersMarshallerAttribute that provides vtable computation and GetOrCreateComInterfaceForObject behavior, and a native IBufferByteAccess implementation with an unmanaged Buffer method that returns the underlying byte pointer and converts exceptions to HRESULTs. --- .../WindowsRuntimeExternalArrayBuffer.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/WinRT.Runtime2/ABI/WindowsRuntime.InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs 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 000000000..c1908daa7 --- /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 From a74dafb67cdfcec6558cb8a23cc582bdd5f80437 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 19 Feb 2026 22:51:55 -0800 Subject: [PATCH 04/31] Support WindowsRuntimeExternalArrayBuffer cases Add handling for the WindowsRuntimeExternalArrayBuffer type in WindowsRuntimeBufferMarshal to extract underlying memory and array segments. Introduces early-return branches that call Buffer()/GetArraySegment() on external array buffers and adds clarifying comments; existing pinned-array handling remains unchanged. --- .../WindowsRuntimeBufferMarshal.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs b/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs index de5fe934f..c07a91b0b 100644 --- a/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs +++ b/src/WinRT.Runtime2/InteropServices/WindowsRuntimeBufferMarshal.cs @@ -48,7 +48,15 @@ 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(); @@ -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(); From 7c6f3edd88cff67bb8b6b9b7551a73aa1ca8ae00 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 20 Feb 2026 16:43:16 -0800 Subject: [PATCH 05/31] Flatten Windows.Storage.Streams paths Remove an extra 'Streams' nesting by relocating several Windows.Storage.Streams files into the Windows.Storage/Streams folder. This cleans up the directory layout to better match namespaces and simplify imports. Files moved: - Windows.Storage.Streams/Buffers/WindowsRuntimeBuffer.cs (from ...Streams/Buffers/...) - Windows.Storage/Streams/IBuffer.cs (from ...Streams/Streams/...) - Windows.Storage/Streams/IInputStream.cs (from ...Streams/Streams/...) - Windows.Storage/Streams/IOutputStream.cs (from ...Streams/Streams/...) - Windows.Storage/Streams/IRandomAccessStream.cs (from ...Streams/Streams/...) - Windows.Storage/Streams/InputStreamOptions.cs (from ...Streams/Streams/...) --- .../Buffers/WindowsRuntimeBuffer.cs | 0 .../Streams/IBuffer.cs | 0 .../Streams/IInputStream.cs | 0 .../Streams/IOutputStream.cs | 0 .../Streams/IRandomAccessStream.cs | 0 .../Streams/InputStreamOptions.cs | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename src/WinRT.Runtime2/{Windows.Storage.Streams => Windows.Storage}/Buffers/WindowsRuntimeBuffer.cs (100%) rename src/WinRT.Runtime2/{Windows.Storage.Streams => Windows.Storage}/Streams/IBuffer.cs (100%) rename src/WinRT.Runtime2/{Windows.Storage.Streams => Windows.Storage}/Streams/IInputStream.cs (100%) rename src/WinRT.Runtime2/{Windows.Storage.Streams => Windows.Storage}/Streams/IOutputStream.cs (100%) rename src/WinRT.Runtime2/{Windows.Storage.Streams => Windows.Storage}/Streams/IRandomAccessStream.cs (100%) rename src/WinRT.Runtime2/{Windows.Storage.Streams => Windows.Storage}/Streams/InputStreamOptions.cs (100%) 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.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 From f9eeb251ee1bf6e59317ac5a3aae064c2be5ef26 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 21 Feb 2026 20:31:37 -0800 Subject: [PATCH 06/31] Use ThrowIfBufferLengthExceedsCapacity helper Replace the manual bounds check and construction of ArgumentOutOfRangeException in the Length setter with ArgumentOutOfRangeException.ThrowIfBufferLengthExceedsCapacity(value, Capacity). This centralizes validation, simplifies the code, and ensures consistent exception details (including HResult) when the buffer length exceeds capacity. --- .../Buffers/WindowsRuntimeExternalArrayBuffer.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs index 34a595efe..a9fb1aba1 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -94,15 +94,7 @@ public uint Length get => (uint)_length; set { - if (value > Capacity) - { - ArgumentOutOfRangeException ex = new(nameof(value), global::Windows.Foundation.SR.Argument_BufferLengthExceedsCapacity) - { - HResult = WellKnownErrorCodes.E_BOUNDS - }; - - throw ex; - } + ArgumentOutOfRangeException.ThrowIfBufferLengthExceedsCapacity(value, Capacity); _length = unchecked((int)value); } From f230ec1470b28d6b2365b077c13b4b31f141d370 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 21 Feb 2026 20:44:42 -0800 Subject: [PATCH 07/31] Add GetSpan to external and pinned buffers Introduce ReadOnlySpan GetSpan() implementations for WindowsRuntimePinnedArrayBuffer and WindowsRuntimeExternalArrayBuffer. Both methods use MemoryMarshal.GetArrayDataReference and Unsafe.Add to create a ReadOnlySpan via MemoryMarshal.CreateReadOnlySpan and are annotated with AggressiveInlining. The pinned variant notes that parameters were validated at construction so offset/capacity checks are skipped; the external variant references the same rationale to avoid extra overhead while providing efficient span access to the buffer data. --- .../Buffers/WindowsRuntimeExternalArrayBuffer.cs | 10 ++++++++++ .../Buffers/WindowsRuntimePinnedArrayBuffer.cs | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs index a9fb1aba1..5a776561d 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -124,6 +124,16 @@ public ArraySegment GetArraySegment() return new(_data, _offset, _length); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetSpan() + { + ref byte pinnedData = ref MemoryMarshal.GetArrayDataReference(_data); + + // See notes in 'WindowsRuntimePinnedArrayBuffer.GetSpan' for why skipping checks here is valid + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); + } + /// /// Pins and returns the address of its data at the right offset. /// diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index 8b694c5c2..bfb796b0b 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -113,4 +113,18 @@ public ArraySegment GetArraySegment() { return new(_pinnedData, _offset, _length); } + + /// + /// Gets a value for the underlying data in this buffer. + /// + /// The resulting value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetSpan() + { + 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 capaciity. + return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); + } } From d2de7c13c0613eae3dde26d2e3068a8543540344 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 24 Feb 2026 22:59:25 -0800 Subject: [PATCH 08/31] Rename GetSpan to GetSpanForCapacity Rename WindowsRuntimePinnedArrayBuffer.GetSpan to GetSpanForCapacity to make the semantics explicit: the returned ReadOnlySpan covers Capacity (not Length). Update WindowsRuntimeExternalArrayBuffer XML doc cref to point to the new member and add a remark documenting the capacity-based length. Method inlining and implementation remain unchanged. --- .../Buffers/WindowsRuntimeExternalArrayBuffer.cs | 4 ++-- .../Buffers/WindowsRuntimePinnedArrayBuffer.cs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs index 5a776561d..6c8a3f348 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -124,9 +124,9 @@ public ArraySegment GetArraySegment() return new(_data, _offset, _length); } - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetSpan() + public ReadOnlySpan GetSpanForCapacity() { ref byte pinnedData = ref MemoryMarshal.GetArrayDataReference(_data); diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index bfb796b0b..5e9ed8bb2 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -118,8 +118,11 @@ public ArraySegment GetArraySegment() /// 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 ReadOnlySpan GetSpan() + public ReadOnlySpan GetSpanForCapacity() { ref byte pinnedData = ref MemoryMarshal.GetArrayDataReference(_pinnedData); From a2edded3e937f9371f50afe0d022038d4d0c1e42 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 24 Feb 2026 23:08:04 -0800 Subject: [PATCH 09/31] Add IBuffer AsBuffer extensions for byte[] Introduce WindowsRuntimeBufferExtensions with three AsBuffer overloads for byte[]: AsBuffer(source), AsBuffer(source, offset, length) and AsBuffer(source, offset, length, capacity). The methods validate arguments and return a WindowsRuntimeExternalArrayBuffer that exposes the byte array as an IBuffer with specified length and capacity. Includes MIT header and preserved commented exception message checks for range/capacity validation. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.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 000000000..46c59039a --- /dev/null +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Windows.Storage.Streams; +using WindowsRuntime.InteropServices; + +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, 0, source.Length, 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, length, 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); + //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 WindowsRuntimeExternalArrayBuffer(source, offset, length, capacity); + } +} From b3a44c05b2fe519ad69ee45203ad7b8c04554f29 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 24 Feb 2026 23:27:55 -0800 Subject: [PATCH 10/31] Use Span for buffer capacity and add CopyTo Change ReadOnlySpan -> Span for GetSpanForCapacity in WindowsRuntimeExternalArrayBuffer and WindowsRuntimePinnedArrayBuffer to allow mutable access and use MemoryMarshal.CreateSpan. Add a new Argument_InvalidIBufferInstance message. Extend WindowsRuntimeBufferExtensions: adjust AsBuffer overloads to use named args, add CopyTo(ReadOnlySpan, IBuffer[, uint]) methods to efficiently copy into IBuffer (with GC.KeepAlive and Length update semantics), and add a private GetSpanForCapacity(IBuffer) helper that retrieves a Span for native, external-array, or pinned-array backed buffers and throws the new argument exception for unsupported IBuffer instances. Also add a few using directives required by the new code. --- .../WindowsRuntimeExternalArrayBuffer.cs | 4 +- .../WindowsRuntimePinnedArrayBuffer.cs | 10 +- .../WindowsRuntimeExceptionMessages.cs | 2 + .../Buffers/WindowsRuntimeBufferExtensions.cs | 98 ++++++++++++++++++- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs index 6c8a3f348..00cf03034 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -126,12 +126,12 @@ public ArraySegment GetArraySegment() /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetSpanForCapacity() + public Span GetSpanForCapacity() { ref byte pinnedData = ref MemoryMarshal.GetArrayDataReference(_data); // See notes in 'WindowsRuntimePinnedArrayBuffer.GetSpan' for why skipping checks here is valid - return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); + return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); } /// diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index 5e9ed8bb2..732594562 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -115,19 +115,19 @@ public ArraySegment GetArraySegment() } /// - /// Gets a value for the underlying data in this buffer. + /// Gets a value for the underlying data in this buffer. /// - /// The resulting value. + /// The resulting value. /// - /// The returned value has a length equal to , not . + /// The returned value has a length equal to , not . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan GetSpanForCapacity() + 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 capaciity. - return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); + return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); } } diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs index fcfd6c39f..bdc57011b 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs @@ -20,6 +20,8 @@ internal static class WindowsRuntimeExceptionMessages public const string Argument_InsufficientSpaceToCopyCollection = "Insufficient space in the target array to copy the collection."; + public const string Argument_InvalidIBufferInstance = "The provided 'IBuffer' instance is not valid, and retrieving its underlying data failed."; + 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."; diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 46c59039a..fb67333fc 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -3,6 +3,7 @@ using System; using Windows.Storage.Streams; +using WindowsRuntime; using WindowsRuntime.InteropServices; namespace Windows.Storage.Buffers; @@ -23,7 +24,7 @@ public static class WindowsRuntimeBufferExtensions /// Thrown if is . public static IBuffer AsBuffer(this byte[] source) { - return AsBuffer(source, 0, source.Length, source.Length); + return AsBuffer(source, offset: 0, length: source.Length, capacity: source.Length); } /// @@ -38,7 +39,7 @@ public static IBuffer AsBuffer(this byte[] source) /// Thrown if the specified range is not valid. public static IBuffer AsBuffer(this byte[] source, int offset, int length) { - return AsBuffer(source, offset, length, length); + return AsBuffer(source, offset: offset, length: length, capacity: length); } /// @@ -64,4 +65,97 @@ public static IBuffer AsBuffer(this byte[] source, int offset, int length, int c return new WindowsRuntimeExternalArrayBuffer(source, offset, length, capacity); } + + /// + /// Copies the contents of a given of values 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. + /// + public static void CopyTo(this ReadOnlySpan source, IBuffer destination) + { + CopyTo(source, destination, destinationIndex: 0); + } + + /// + /// Copies the contents of a given of values 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. + /// + public static void CopyTo(this ReadOnlySpan source, IBuffer destination, uint destinationIndex) + { + ArgumentNullException.ThrowIfNull(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 the source span is empty, just stop here immediately and skip all overhead of preparing the target range + if (source.IsEmpty) + { + return; + } + + Span destinationSpan = GetSpanForCapacity(destination)[(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; + } + } + + /// + /// 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) + { + // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', but returning a 'Span' instead + if (buffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) + { + using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); + + byte* bufferPtr; + + HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &bufferPtr); + + RestrictedErrorInfo.ThrowExceptionForHR(hresult); + + return new(bufferPtr, checked((int)buffer.Capacity)); + } + + // If buffer is backed by a managed array, return it + if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + { + return externalArrayBuffer.GetSpanForCapacity(); + } + + // Same as above for pinned arrays as well + if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) + { + return pinnedArrayBuffer.GetSpanForCapacity(); + } + + throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + } } From 0261ad66fa76115d32d9f28085b44ffc063138a3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 24 Feb 2026 23:38:16 -0800 Subject: [PATCH 11/31] Add byte[] CopyTo overloads and improve docs Refine XML docs for ReadOnlySpan.CopyTo overloads (singular "value" wording) and add missing tags documenting ArgumentNullException, ArgumentException and ArgumentOutOfRangeException cases. Introduce two new byte[] CopyTo overloads: CopyTo(byte[] source, IBuffer destination) and CopyTo(byte[] source, int sourceIndex, IBuffer destination, uint destinationIndex, int count). Both delegate to the existing ReadOnlySpan implementations (using AsSpan) and perform null checks before calling into the span-based logic. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index fb67333fc..2428e6472 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -67,7 +67,7 @@ public static IBuffer AsBuffer(this byte[] source, int offset, int length, int c } /// - /// Copies the contents of a given of values to a target instance. + /// Copies the contents of a given value to a target instance. /// /// The value to copy from. /// The destination instance to copy data to. @@ -76,13 +76,15 @@ public static IBuffer AsBuffer(this byte[] source, int offset, int length, int c /// 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. public static void CopyTo(this ReadOnlySpan source, IBuffer destination) { CopyTo(source, destination, destinationIndex: 0); } /// - /// Copies the contents of a given of values to a target instance. + /// Copies the contents of a given value to a target instance. /// /// The value to copy from. /// The destination instance to copy data to. @@ -92,6 +94,11 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination) /// 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. + /// public static void CopyTo(this ReadOnlySpan source, IBuffer destination, uint destinationIndex) { ArgumentNullException.ThrowIfNull(destination); @@ -120,6 +127,54 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination, u } } + /// + /// 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. + public static void CopyTo(this byte[] source, IBuffer destination) + { + ArgumentNullException.ThrowIfNull(source); + + CopyTo(source.AsSpan(), destination, destinationIndex: 0); + } + + /// + /// Copies 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 length 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. + /// + 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); + } + /// /// Gets a value for the underlying data in the specified buffer. /// From be42f2fbf28feb3193b56cd1ad1e46b8c76edfd3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 10:31:22 -0800 Subject: [PATCH 12/31] Add IBuffer-to-Span CopyTo overloads Add two CopyTo overloads to copy from IBuffer to Span (full-buffer and ranged variants). Implement argument checks, early-return for zero-length copies, use GetSpanForCapacity(...).Slice for span slicing and call GC.KeepAlive(source) after the copy. Also replace a range-based span slice with explicit Slice, add a pragma to suppress IDE0057, and update XML docs and exception wording to better describe destination capacity requirements. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 2428e6472..555fa8fa3 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -6,6 +6,8 @@ using WindowsRuntime; using WindowsRuntime.InteropServices; +#pragma warning disable IDE0057 + namespace Windows.Storage.Buffers; /// @@ -111,7 +113,7 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination, u return; } - Span destinationSpan = GetSpanForCapacity(destination)[(int)destinationIndex..]; + Span destinationSpan = GetSpanForCapacity(destination).Slice(start: (int)destinationIndex); source.CopyTo(destinationSpan); @@ -147,7 +149,7 @@ public static void CopyTo(this byte[] source, IBuffer destination) } /// - /// Copies range of bytes in the specified byte array to a target instance. + /// 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. @@ -162,7 +164,7 @@ public static void CopyTo(this byte[] source, IBuffer destination) /// Thrown if or are . /// /// Thrown if is less than 0, if it exceeds the length of , - /// or if exceeds the length of . + /// or if exceeds the capacity of . /// /// /// Thrown if exceeds the value of the property for , @@ -175,6 +177,52 @@ public static void CopyTo(this byte[] source, int sourceIndex, IBuffer destinati CopyTo(source.AsSpan(start: sourceIndex, length: count), destination, destinationIndex: destinationIndex); } + /// + /// Copies the contents of a given instance to a target byte array. + /// + /// 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. + 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. + public static void CopyTo(this IBuffer source, uint sourceIndex, Span destination, int count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentOutOfRangeException.ThrowIfNegative(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 there are no values to copy, just stop here immediately and skip all overhead of preparing the target range + if (count == 0) + { + return; + } + + Span sourceSpan = GetSpanForCapacity(source).Slice(start: (int)sourceIndex, length: count); + + sourceSpan.CopyTo(destination); + + GC.KeepAlive(source); + } + /// /// Gets a value for the underlying data in the specified buffer. /// From 3b1b900b66b94927eb3cc4eb9724f7530c580cac Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 10:36:37 -0800 Subject: [PATCH 13/31] Add byte[] overloads for IBuffer.CopyTo Add two convenience overloads for copying IBuffer contents to byte arrays: CopyTo(this IBuffer, byte[]) and CopyTo(this IBuffer, uint sourceIndex, byte[] destination, int destinationIndex, int count). Both perform null checks and forward to the existing Span-based CopyTo implementation. Also update the XML summary on the Span-based CopyTo to correctly reference Span. These changes make it easier for callers using arrays to copy buffer data without manually creating spans. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 555fa8fa3..37bb61ff1 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -178,7 +178,7 @@ public static void CopyTo(this byte[] source, int sourceIndex, IBuffer destinati } /// - /// Copies the contents of a given instance to a target byte array. + /// Copies the contents of a given instance to a target value. /// /// The instance to copy from. /// The destination value to copy data to. @@ -223,6 +223,43 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, Span dest 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. + 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. + 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); + } + /// /// Gets a value for the underlying data in the specified buffer. /// From 676fec2d0211a690fc62f63ab011900e7d4dcd23 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 10:49:43 -0800 Subject: [PATCH 14/31] Add IBuffer CopyTo overloads and safety checks Add two IBuffer.CopyTo overloads to copy entire buffers or a specified range between IBuffer instances, including XML docs. Introduce System.Diagnostics and Debug.Assert checks for int.MaxValue bounds, early-return for zero-length copies, Span-based copy logic, GC.KeepAlive calls, and update destination.Length after copy. Also add Debug.Assert guards in existing copy helpers and argument null checks to improve safety and correctness. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 37bb61ff1..f1cfd1f19 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using Windows.Storage.Streams; using WindowsRuntime; using WindowsRuntime.InteropServices; @@ -113,6 +114,8 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination, u return; } + Debug.Assert(destinationIndex <= int.MaxValue); + Span destinationSpan = GetSpanForCapacity(destination).Slice(start: (int)destinationIndex); source.CopyTo(destinationSpan); @@ -216,6 +219,8 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, Span dest return; } + Debug.Assert(sourceIndex <= int.MaxValue); + Span sourceSpan = GetSpanForCapacity(source).Slice(start: (int)sourceIndex, length: count); sourceSpan.CopyTo(destination); @@ -260,6 +265,68 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, byte[] destinat 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. + 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. + /// + public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destination, uint destinationIndex, uint count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(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 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; + } + } + /// /// Gets a value for the underlying data in the specified buffer. /// From ab68af865f563b6159ba920080968725c05e9bba Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 10:58:59 -0800 Subject: [PATCH 15/31] Add IBuffer.ToArray extension methods Introduce two ToArray extension overloads for IBuffer: a parameterless variant that copies the entire buffer and an overload that accepts a sourceIndex and count. Both perform null/argument validation, handle a zero-length fast path (returning an empty array), allocate an uninitialized byte[] via GC.AllocateUninitializedArray, and copy data using the buffer's CopyTo method. Some additional range checks (Array.MaxLength and capacity/remaining-space checks) are present but commented out. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index f1cfd1f19..f61ca4b07 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -327,6 +327,56 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destina } } + /// + /// 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 . + public static byte[] ToArray(this IBuffer source) + { + ArgumentNullException.ThrowIfNull(source); + // if (source.Length > Array.MaxLength) throw new ArgumentOutOfRangeException("The specified buffer has a length that exceeds the maximum array 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. + /// + public static byte[] ToArray(this IBuffer source, uint sourceIndex, int count) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentOutOfRangeException.ThrowIfNegative(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 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; + } + /// /// Gets a value for the underlying data in the specified buffer. /// From e598eb19efc905dd2991a8cb155ab64a8f9e763b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 11:46:54 -0800 Subject: [PATCH 16/31] Add IsSameData and TryGetSpan helpers Introduce IsSameData(IBuffer, IBuffer?) to detect whether two IBuffer instances share the same underlying memory (handles both managed and native-backed buffers). Add TryGetNativeSpanForCapacity and TryGetManagedSpanForCapacity to safely obtain Span for a buffer's Capacity without throwing/pinning, and refactor GetSpanForCapacity to use these helpers. Document potential exceptions for IBufferByteAccess.Buffer failures on various CopyTo/ToArray APIs and add necessary using/imports and attributes. These changes avoid unnecessary pinning, centralize native/managed span retrieval, and improve diagnostics when interacting with IBuffer internals. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 147 +++++++++++++++++- 1 file changed, 142 insertions(+), 5 deletions(-) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index f61ca4b07..4c4202bc3 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -3,6 +3,9 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Windows.Storage.Streams; using WindowsRuntime; using WindowsRuntime.InteropServices; @@ -81,6 +84,7 @@ public static IBuffer AsBuffer(this byte[] source, int offset, int length, int c /// /// 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); @@ -102,6 +106,7 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination) /// 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); @@ -144,6 +149,7 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination, u /// /// 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); @@ -173,6 +179,7 @@ public static void CopyTo(this byte[] source, IBuffer destination) /// 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); @@ -187,6 +194,7 @@ public static void CopyTo(this byte[] source, int sourceIndex, IBuffer destinati /// 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)); @@ -205,6 +213,7 @@ public static void CopyTo(this IBuffer source, Span destination) /// 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); @@ -235,6 +244,7 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, Span dest /// 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); @@ -257,6 +267,7 @@ public static void CopyTo(this IBuffer source, byte[] destination) /// 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); @@ -272,6 +283,7 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, byte[] destinat /// 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); @@ -293,6 +305,7 @@ public static void CopyTo(this IBuffer source, IBuffer destination) /// 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); @@ -335,6 +348,7 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destina /// 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); @@ -357,6 +371,7 @@ public static byte[] ToArray(this IBuffer source) /// 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); @@ -377,6 +392,85 @@ public static byte[] ToArray(this IBuffer source, uint sourceIndex, int 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 = TryGetManagedSpanForCapacity(buffer, out Span span); + bool otherBufferIsManaged = TryGetManagedSpanForCapacity(otherBuffer, out Span otherSpan); + + // 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 memory address. + if (bufferIsManaged) + { + return Unsafe.AreSame( + left: in MemoryMarshal.GetReference(span), + right: in MemoryMarshal.GetReference(otherSpan)); + } + + // 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 (buffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject && + otherBuffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } otherBufferObject) + { + using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); + using WindowsRuntimeObjectReferenceValue otherBufferByteAccessValue = otherBufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); + + byte* bufferPtr; + byte* otherBufferPtr; + + HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &bufferPtr); + + RestrictedErrorInfo.ThrowExceptionForHR(hresult); + + hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &otherBufferPtr); + + RestrictedErrorInfo.ThrowExceptionForHR(hresult); + + return bufferPtr == otherBufferPtr; + } + + return false; + } + /// /// Gets a value for the underlying data in the specified buffer. /// @@ -385,7 +479,26 @@ public static byte[] ToArray(this IBuffer source, uint sourceIndex, int count) /// /// The returned value has a length equal to , not . /// - private static unsafe Span GetSpanForCapacity(IBuffer buffer) + private static Span GetSpanForCapacity(IBuffer buffer) + { + if (TryGetNativeSpanForCapacity(buffer, out Span span) || TryGetManagedSpanForCapacity(buffer, out span)) + { + return span; + } + + throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + } + + /// + /// Tries to get a value for the underlying data in the specified buffer, only if backed by native memory. + /// + /// The input instance. + /// The resulting value, if retrieved. + /// Whether could be retrieved. + /// + /// The returned value has a length equal to , not . + /// + private static unsafe bool TryGetNativeSpanForCapacity(IBuffer buffer, out Span span) { // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', but returning a 'Span' instead if (buffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) @@ -398,21 +511,45 @@ private static unsafe Span GetSpanForCapacity(IBuffer buffer) RestrictedErrorInfo.ThrowExceptionForHR(hresult); - return new(bufferPtr, checked((int)buffer.Capacity)); + span = new(bufferPtr, checked((int)buffer.Capacity)); + + return true; } + span = default; + + return false; + } + + /// + /// 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 buffer is backed by a managed array, return it if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) { - return externalArrayBuffer.GetSpanForCapacity(); + span = externalArrayBuffer.GetSpanForCapacity(); + + return true; } // Same as above for pinned arrays as well if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) { - return pinnedArrayBuffer.GetSpanForCapacity(); + span = pinnedArrayBuffer.GetSpanForCapacity(); + + return true; } - throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + span = default; + + return false; } } From 40815510d56b1b81310d9a52f6ff94767378be91 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 12:07:52 -0800 Subject: [PATCH 17/31] Add IBuffer.GetByte extension method Introduce GetByte(this IBuffer, uint) to WindowsRuntimeBufferExtensions with XML docs. The method validates the source is not null, obtains a Span via GetSpanForCapacity, reads the byte at the specified offset, and calls GC.KeepAlive to ensure the buffer remains alive. Provides a convenient, safe way to read a single byte from an IBuffer instance. --- .../Buffers/WindowsRuntimeBufferExtensions.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 4c4202bc3..9facd6317 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -471,6 +471,29 @@ public static unsafe bool IsSameData(this IBuffer buffer, [NotNullWhen(true)] IB return false; } + /// + /// 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); + //if (source.Length <= byteOffset) throw new ArgumentException("The specified buffer offset is not within the buffer 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. /// From 64c8b6d68e4e5645289d113be548f44e644603ba Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 12:18:03 -0800 Subject: [PATCH 18/31] Add IBuffer-backed MemoryStream wrapper Introduce WindowsRuntimeBufferMemoryStream, an internal sealed MemoryStream implementation backed by a Windows.Storage.Streams.IBuffer. The stream constructor accepts an IBuffer, a backing byte[] and an offset and initializes the stream length from the buffer. Overrides SetLength, Write (sync and span), WriteAsync (task and ValueTask) and WriteByte to keep the IBuffer.Length in sync with the stream. This supports managed-to-WinRT buffer interop without parameter validation in the constructor. --- .../WindowsRuntimeBufferMemoryStream.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs 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 000000000..b82e55c4b --- /dev/null +++ b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.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 managed instance. +/// +internal sealed class WindowsRuntimeBufferMemoryStream : MemoryStream +{ + 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; + } +} From b6819d2dcc9bfd267b221f7f0c532377bbf6a14d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 13:29:06 -0800 Subject: [PATCH 19/31] Add UnmanagedMemoryStream backed by IBuffer Introduce WindowsRuntimeBufferUnmanagedMemoryStream: an UnmanagedMemoryStream implementation backed by a native IBuffer. The new class exposes a constructor accepting an IBuffer and native byte* memory, overrides SetLength and various Write APIs (sync/async/Span/byte) to keep the IBuffer.Length in sync, and uses FileAccess.ReadWrite. Also add a private _buffer field (with comment) to WindowsRuntimeBufferMemoryStream.cs. Note: the UnmanagedMemoryStream constructor does not validate parameters and uses unsafe pointer input. --- .../WindowsRuntimeBufferMemoryStream.cs | 3 + ...ndowsRuntimeBufferUnmanagedMemoryStream.cs | 82 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferUnmanagedMemoryStream.cs diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs index b82e55c4b..20a187ad8 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/MemoryStreams/WindowsRuntimeBufferMemoryStream.cs @@ -14,6 +14,9 @@ namespace WindowsRuntime.InteropServices; /// internal sealed class WindowsRuntimeBufferMemoryStream : MemoryStream { + /// + /// The instance to back the stream. + /// private readonly IBuffer _buffer; /// 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 000000000..b5af9aaaa --- /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; + } +} From bab564b31f26d972dd2f7bfbe98a3e1d6de476ad Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 14:22:39 -0800 Subject: [PATCH 20/31] Add GetArray and IBuffer AsStream helpers Expose underlying array accessors on WindowsRuntimeExternalArrayBuffer and WindowsRuntimePinnedArrayBuffer (GetArray(out int offset)), and add MemoryStream <-> IBuffer helpers in WindowsRuntimeBufferExtensions: GetWindowsRuntimeBuffer overloads to create IBuffer from MemoryStream, and AsStream to wrap an IBuffer as a Stream (handles array-backed and native buffers). Also add System.IO using and fix inverted logic in GetSpanForCapacity to correctly throw for invalid IBuffer instances. These changes enable sharing memory between MemoryStream and IBuffer and provide a convenient Stream view over IBuffer data. --- .../WindowsRuntimeExternalArrayBuffer.cs | 8 + .../WindowsRuntimePinnedArrayBuffer.cs | 12 ++ .../Buffers/WindowsRuntimeBufferExtensions.cs | 139 +++++++++++++++++- 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs index 00cf03034..dce1ac718 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -118,6 +118,14 @@ public uint Length return PinData(); } + /// + public byte[] GetArray(out int offset) + { + offset = _offset; + + return _data; + } + /// public ArraySegment GetArraySegment() { diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index 732594562..3bff8e90d 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -105,6 +105,18 @@ public uint Length 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. /// diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 9facd6317..f3ebd10ce 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Windows.Storage.Streams; @@ -471,6 +472,138 @@ public static unsafe bool IsSameData(this IBuffer buffer, [NotNullWhen(true)] IB 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)) + { + //throw new UnauthorizedAccessException(global::Windows.Storage.Streams.SR.UnauthorizedAccess_InternalBuffer); + } + + 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); + //if (stream.Length < position) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_StreamPositionBeyondEOS); + + // Extract the underlying buffer from the stream (same as above) + if (!stream.TryGetBuffer(out ArraySegment arraySegment)) + { + //throw new UnauthorizedAccessException(global::Windows.Storage.Streams.SR.UnauthorizedAccess_InternalBuffer); + } + + 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 buffer is backed by a managed array, unwrap it and use it for the stream + if (source is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + { + byte[] array = externalArrayBuffer.GetArray(out int offset); + + return new WindowsRuntimeBufferMemoryStream(source, array, offset); + } + + // Same as above for pinned arrays as well + if (source is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) + { + byte[] array = pinnedArrayBuffer.GetArray(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 (source is not WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) + { + throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + } + + // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', just tweaked for this method + using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); + + byte* bufferPtr; + + HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &bufferPtr); + + RestrictedErrorInfo.ThrowExceptionForHR(hresult); + + return new WindowsRuntimeBufferUnmanagedMemoryStream(source, bufferPtr); + } + /// /// Returns the byte at the specified offset in the specified instance. /// @@ -504,12 +637,12 @@ public static byte GetByte(this IBuffer source, uint byteOffset) /// private static Span GetSpanForCapacity(IBuffer buffer) { - if (TryGetNativeSpanForCapacity(buffer, out Span span) || TryGetManagedSpanForCapacity(buffer, out span)) + if (!TryGetNativeSpanForCapacity(buffer, out Span span) && !TryGetManagedSpanForCapacity(buffer, out span)) { - return span; + throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); } - throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + return span; } /// From 2636e1aa6da4de00458109edfa0de0896508bf31 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 15:23:36 -0800 Subject: [PATCH 21/31] Use exception helpers for buffer checks Add a set of ArgumentException/ArgumentOutOfRange/UnauthorizedAccess helpers to WindowsRuntimeExceptionExtensions and corresponding message constants to WindowsRuntimeExceptionMessages. Replace in-file commented/manual validation checks in WindowsRuntimeBufferExtensions with calls to the new ThrowIf* helpers (e.g. ThrowIfInsufficientArrayElementsAfterOffset, ThrowIfInsufficientBufferCapacity, ThrowIfBufferIndexExceedsCapacity/Length, ThrowIfInsufficientSpaceInSourceBuffer/TargetBuffer, ThrowIfStreamPositionBeyondEndOfStream, ThrowIfBufferLengthExceedsArrayMaxLength, ThrowInvalidIBufferInstance, ThrowInternalBufferAccess). Helpers are annotated for inlining/stack-trace hiding and centralize error text creation for clearer, consistent validation and minor performance improvements. --- .../WindowsRuntimeExceptionExtensions.cs | 226 ++++++++++++++++++ .../WindowsRuntimeExceptionMessages.cs | 18 ++ .../Buffers/WindowsRuntimeBufferExtensions.cs | 42 ++-- 3 files changed, 265 insertions(+), 21 deletions(-) diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs index 911cc2d94..763bd47a4 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,212 @@ 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_BufferIndexExceedsLength); + + 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(); + } + } + + /// + /// Throws an indicating that the provided IBuffer instance is not valid. + /// + /// Always thrown. + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowInvalidIBufferInstance() + { + throw GetInvalidIBufferInstanceException(); + } + + /// + /// 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 bdc57011b..9846d7306 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs @@ -14,14 +14,30 @@ 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_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."; @@ -44,5 +60,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/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index f3ebd10ce..90eb540ac 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -66,9 +66,9 @@ public static IBuffer AsBuffer(this byte[] source, int offset, int length, int c ArgumentOutOfRangeException.ThrowIfNegative(offset); ArgumentOutOfRangeException.ThrowIfNegative(length); ArgumentOutOfRangeException.ThrowIfNegative(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); + ArgumentException.ThrowIfInsufficientArrayElementsAfterOffset(source.Length, offset, length); + ArgumentException.ThrowIfInsufficientArrayElementsAfterOffset(source.Length, offset, capacity); + ArgumentException.ThrowIfInsufficientBufferCapacity(capacity, length); return new WindowsRuntimeExternalArrayBuffer(source, offset, length, capacity); } @@ -111,8 +111,8 @@ public static void CopyTo(this ReadOnlySpan source, IBuffer destination) public static void CopyTo(this ReadOnlySpan source, IBuffer destination, uint destinationIndex) { ArgumentNullException.ThrowIfNull(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); + 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) @@ -219,9 +219,9 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, Span dest { ArgumentNullException.ThrowIfNull(source); ArgumentOutOfRangeException.ThrowIfNegative(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); + 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) @@ -311,10 +311,10 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destina { ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(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); + 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) @@ -353,7 +353,7 @@ public static void CopyTo(this IBuffer source, uint sourceIndex, IBuffer destina public static byte[] ToArray(this IBuffer source) { ArgumentNullException.ThrowIfNull(source); - // if (source.Length > Array.MaxLength) throw new ArgumentOutOfRangeException("The specified buffer has a length that exceeds the maximum array length."); + ArgumentOutOfRangeException.ThrowIfBufferLengthExceedsArrayMaxLength(source.Length); return ToArray(source, sourceIndex: 0, count: (int)source.Length); } @@ -377,8 +377,8 @@ public static byte[] ToArray(this IBuffer source, uint sourceIndex, int count) { ArgumentNullException.ThrowIfNull(source); ArgumentOutOfRangeException.ThrowIfNegative(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); + 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) @@ -496,7 +496,7 @@ public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream stream) // buffer instance if this succeeds. Otherwise, there's no way to actually get the memory area we need. if (!stream.TryGetBuffer(out ArraySegment arraySegment)) { - //throw new UnauthorizedAccessException(global::Windows.Storage.Streams.SR.UnauthorizedAccess_InternalBuffer); + UnauthorizedAccessException.ThrowInternalBufferAccess(); } Debug.Assert(stream.Length <= int.MaxValue); @@ -543,12 +543,12 @@ public static IBuffer GetWindowsRuntimeBuffer(this MemoryStream stream, int posi ArgumentNullException.ThrowIfNull(stream); ArgumentOutOfRangeException.ThrowIfNegative(position); ArgumentOutOfRangeException.ThrowIfNegative(length); - //if (stream.Length < position) throw new ArgumentException(global::Windows.Storage.Streams.SR.Argument_StreamPositionBeyondEOS); + ArgumentException.ThrowIfStreamPositionBeyondEndOfStream(stream.Length, position); // Extract the underlying buffer from the stream (same as above) if (!stream.TryGetBuffer(out ArraySegment arraySegment)) { - //throw new UnauthorizedAccessException(global::Windows.Storage.Streams.SR.UnauthorizedAccess_InternalBuffer); + UnauthorizedAccessException.ThrowInternalBufferAccess(); } int bufferOffset = arraySegment.Offset + position; @@ -589,7 +589,7 @@ public static unsafe Stream AsStream(this IBuffer source) // At this point the buffer must be a native object wrapper, so validate that it is the case if (source is not WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) { - throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + throw ArgumentException.GetInvalidIBufferInstanceException(); } // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', just tweaked for this method @@ -616,7 +616,7 @@ public static unsafe Stream AsStream(this IBuffer source) public static byte GetByte(this IBuffer source, uint byteOffset) { ArgumentNullException.ThrowIfNull(source); - //if (source.Length <= byteOffset) throw new ArgumentException("The specified buffer offset is not within the buffer length."); + ArgumentException.ThrowIfBufferOffsetOutOfRange(byteOffset, source.Length); Span span = GetSpanForCapacity(source); @@ -639,7 +639,7 @@ private static Span GetSpanForCapacity(IBuffer buffer) { if (!TryGetNativeSpanForCapacity(buffer, out Span span) && !TryGetManagedSpanForCapacity(buffer, out span)) { - throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_InvalidIBufferInstance); + ArgumentException.ThrowInvalidIBufferInstance(); } return span; From e9f4fd405216a507afeb5ce8aa490cc9a9ebd711 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 15:26:41 -0800 Subject: [PATCH 22/31] Remove WindowsRuntimeBuffer and related files Delete WinRT buffer implementation and helpers for Windows.Storage.Streams (IBufferByteAccess.cs, IMarshal.cs, IMemoryBufferByteAccess.cs, WindowsRuntimeBuffer.cs, WindowsRuntimeBufferExtensions.cs) and remove their entries from src/cswinrt/cswinrt.vcxproj and cswinrt.vcxproj.filters. Cleans up deprecated/unneeded buffer wrapper code and project file references. --- src/cswinrt/cswinrt.vcxproj | 5 - src/cswinrt/cswinrt.vcxproj.filters | 15 - .../IBufferByteAccess.cs | 63 -- .../Windows.Storage.Streams/IMarshal.cs | 294 -------- .../IMemoryBufferByteAccess.cs | 20 - .../WindowsRuntimeBuffer.cs | 288 -------- .../WindowsRuntimeBufferExtensions.cs | 628 ------------------ 7 files changed, 1313 deletions(-) delete mode 100644 src/cswinrt/strings/additions/Windows.Storage.Streams/IBufferByteAccess.cs delete mode 100644 src/cswinrt/strings/additions/Windows.Storage.Streams/IMarshal.cs delete mode 100644 src/cswinrt/strings/additions/Windows.Storage.Streams/IMemoryBufferByteAccess.cs delete mode 100644 src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBuffer.cs delete mode 100644 src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs diff --git a/src/cswinrt/cswinrt.vcxproj b/src/cswinrt/cswinrt.vcxproj index 5cb779ae0..5901eed4d 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 1b4d681b0..1b832cd1b 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/strings/additions/Windows.Storage.Streams/IBufferByteAccess.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/IBufferByteAccess.cs deleted file mode 100644 index a4c1e0916..000000000 --- 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 8bab861e3..000000000 --- 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 f5a0041c4..000000000 --- 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/WindowsRuntimeBuffer.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBuffer.cs deleted file mode 100644 index 1a522bcb2..000000000 --- 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 45433968d..000000000 --- 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 From b1f07811e5449d2d97a75ac5d1c5f67b5f342201 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 15:38:43 -0800 Subject: [PATCH 23/31] Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 90eb540ac..8f78ebcea 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -462,7 +462,7 @@ public static unsafe bool IsSameData(this IBuffer buffer, [NotNullWhen(true)] IB RestrictedErrorInfo.ThrowExceptionForHR(hresult); - hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &otherBufferPtr); + hresult = IBufferByteAccessVftbl.BufferUnsafe(otherBufferByteAccessValue.GetThisPtrUnsafe(), &otherBufferPtr); RestrictedErrorInfo.ThrowExceptionForHR(hresult); From c842d885eab04523fc853d1a0cbd45cfd2a9411f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 15:39:46 -0800 Subject: [PATCH 24/31] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Buffers/WindowsRuntimeExternalArrayBuffer.cs | 2 +- .../InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs index dce1ac718..a7abaa801 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimeExternalArrayBuffer.cs @@ -175,7 +175,7 @@ public Span GetSpanForCapacity() 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, than just stop and dispose the pinned GC handle + // 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(); } diff --git a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs index 3bff8e90d..8fbce18f6 100644 --- a/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs +++ b/src/WinRT.Runtime2/InteropServices/Buffers/WindowsRuntimePinnedArrayBuffer.cs @@ -139,7 +139,7 @@ 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 capaciity. + // so we can avoid the overhead of checking the offset and capacity. return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref pinnedData, _offset), _capacity); } } From fdecafb595483e72cdee91810ef26f7803f710fb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 15:42:29 -0800 Subject: [PATCH 25/31] Use BufferOffset exception message and add constant Replace the incorrect Argument_BufferIndexExceedsLength usage with a new Argument_BufferOffsetExceedsLength in WindowsRuntimeExceptionExtensions to provide a more accurate error for offset checks. Add the corresponding constant message to WindowsRuntimeExceptionMessages. --- .../Properties/WindowsRuntimeExceptionExtensions.cs | 2 +- .../Properties/WindowsRuntimeExceptionMessages.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs index 763bd47a4..1dadb6650 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs @@ -504,7 +504,7 @@ public static void ThrowIfBufferOffsetOutOfRange(uint offset, uint length) [DoesNotReturn] [StackTraceHidden] static void ThrowArgumentException() - => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_BufferIndexExceedsLength); + => throw new ArgumentException(WindowsRuntimeExceptionMessages.Argument_BufferOffsetExceedsLength); if (offset >= length) { diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs index 9846d7306..c5fc782f3 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionMessages.cs @@ -18,6 +18,8 @@ internal static class WindowsRuntimeExceptionMessages 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."; From 24d95347f9468b8e1f862f8ed70e819f6d8cef25 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 16:00:46 -0800 Subject: [PATCH 26/31] Refactor IBuffer unwrap and access helpers Introduce clearer managed/native unwrapping helpers and simplify buffer logic. Adds TryGetArray and TryGetData to handle managed arrays (array + offset) and native pointers respectively, replaces span-based helpers, and updates equality, span retrieval and stream creation paths to use the new helpers. GetSpanForCapacity now prefers managed arrays, then native data, and throws for unrecognized buffers. Also removes the now-unused ThrowInvalidIBufferInstance helper from WindowsRuntimeExceptionExtensions.cs. These changes reduce low-level unsafe span usage, centralize native access, and make equality/stream logic rely on array/offset or raw pointers. --- .../WindowsRuntimeExceptionExtensions.cs | 11 -- .../Buffers/WindowsRuntimeBufferExtensions.cs | 147 +++++++++--------- 2 files changed, 75 insertions(+), 83 deletions(-) diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs index 1dadb6650..73df46611 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs @@ -577,17 +577,6 @@ static void ThrowArgumentException() } } - /// - /// Throws an indicating that the provided IBuffer instance is not valid. - /// - /// Always thrown. - [DoesNotReturn] - [StackTraceHidden] - public static void ThrowInvalidIBufferInstance() - { - throw GetInvalidIBufferInstanceException(); - } - /// /// Creates an indicating that the provided IBuffer instance is not valid. /// diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index 8f78ebcea..cffa8e0d6 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -425,8 +425,8 @@ public static unsafe bool IsSameData(this IBuffer buffer, [NotNullWhen(true)] IB return true; } - bool bufferIsManaged = TryGetManagedSpanForCapacity(buffer, out Span span); - bool otherBufferIsManaged = TryGetManagedSpanForCapacity(otherBuffer, out Span otherSpan); + 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) @@ -437,38 +437,23 @@ public static unsafe bool IsSameData(this IBuffer buffer, [NotNullWhen(true)] IB // 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 memory address. + // 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 Unsafe.AreSame( - left: in MemoryMarshal.GetReference(span), - right: in MemoryMarshal.GetReference(otherSpan)); + 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 (buffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject && - otherBuffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } otherBufferObject) + if (TryGetData(buffer, out byte* data) && TryGetData(otherBuffer, out byte* otherData)) { - using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); - using WindowsRuntimeObjectReferenceValue otherBufferByteAccessValue = otherBufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); - - byte* bufferPtr; - byte* otherBufferPtr; - - HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &bufferPtr); - - RestrictedErrorInfo.ThrowExceptionForHR(hresult); - - hresult = IBufferByteAccessVftbl.BufferUnsafe(otherBufferByteAccessValue.GetThisPtrUnsafe(), &otherBufferPtr); - - RestrictedErrorInfo.ThrowExceptionForHR(hresult); - - return bufferPtr == otherBufferPtr; + 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; } @@ -570,38 +555,20 @@ public static unsafe Stream AsStream(this IBuffer source) { ArgumentNullException.ThrowIfNull(source); - // If buffer is backed by a managed array, unwrap it and use it for the stream - if (source is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) + // If the buffer is backed by a managed array, create a stream around it + if (TryGetArray(source, out byte[]? array, out int offset)) { - byte[] array = externalArrayBuffer.GetArray(out int offset); - - return new WindowsRuntimeBufferMemoryStream(source, array, offset); - } - - // Same as above for pinned arrays as well - if (source is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) - { - byte[] array = pinnedArrayBuffer.GetArray(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 (source is not WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) + if (TryGetData(source, out byte* data)) { - throw ArgumentException.GetInvalidIBufferInstanceException(); + return new WindowsRuntimeBufferUnmanagedMemoryStream(source, data); } - // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', just tweaked for this method - using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); - - byte* bufferPtr; - - HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &bufferPtr); - - RestrictedErrorInfo.ThrowExceptionForHR(hresult); - - return new WindowsRuntimeBufferUnmanagedMemoryStream(source, bufferPtr); + // The buffer is not one we can wrap in a stream + throw ArgumentException.GetInvalidIBufferInstanceException(); } /// @@ -635,18 +602,27 @@ public static byte GetByte(this IBuffer source, uint byteOffset) /// /// The returned value has a length equal to , not . /// - private static Span GetSpanForCapacity(IBuffer buffer) + private static unsafe Span GetSpanForCapacity(IBuffer buffer) { - if (!TryGetNativeSpanForCapacity(buffer, out Span span) && !TryGetManagedSpanForCapacity(buffer, out span)) + // Check for managed buffers first + if (TryGetManagedSpanForCapacity(buffer, out Span span)) + { + return span; + } + + // Check for native buffers next + if (TryGetData(buffer, out byte* data)) { - ArgumentException.ThrowInvalidIBufferInstance(); + return new(data, checked((int)buffer.Capacity)); } - return span; + // 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 native memory. + /// 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. @@ -654,44 +630,70 @@ private static Span GetSpanForCapacity(IBuffer buffer) /// /// The returned value has a length equal to , not . /// - private static unsafe bool TryGetNativeSpanForCapacity(IBuffer buffer, out Span span) + private static bool TryGetManagedSpanForCapacity(IBuffer buffer, out Span span) { - // Equivalent logic as 'WindowsRuntimeBufferMarshal.TryGetDataUnsafe', but returning a 'Span' instead - if (buffer is WindowsRuntimeObject { HasUnwrappableNativeObjectReference: true } bufferObject) + // If the buffer is backed by a managed array, return it + if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) { - using WindowsRuntimeObjectReferenceValue bufferByteAccessValue = bufferObject.NativeObjectReference.AsValue(WellKnownInterfaceIIDs.IID_IBufferByteAccess); + span = externalArrayBuffer.GetSpanForCapacity(); + + return true; + } - byte* bufferPtr; + // Same as above for pinned arrays as well + if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) + { + span = pinnedArrayBuffer.GetSpanForCapacity(); - HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), &bufferPtr); + 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); - RestrictedErrorInfo.ThrowExceptionForHR(hresult); + fixed (byte** dataPtr = &data) + { + HRESULT hresult = IBufferByteAccessVftbl.BufferUnsafe(bufferByteAccessValue.GetThisPtrUnsafe(), dataPtr); - span = new(bufferPtr, checked((int)buffer.Capacity)); + RestrictedErrorInfo.ThrowExceptionForHR(hresult); + } return true; } - span = default; + data = null; return false; } /// - /// Tries to get a value for the underlying data in the specified buffer, only if backed by a managed array. + /// Tries to get the underlying array for 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) + /// 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 buffer is backed by a managed array, return it + // If the buffer is backed by a managed array, unwrap it if (buffer is WindowsRuntimeExternalArrayBuffer externalArrayBuffer) { - span = externalArrayBuffer.GetSpanForCapacity(); + array = externalArrayBuffer.GetArray(out offset); return true; } @@ -699,12 +701,13 @@ private static bool TryGetManagedSpanForCapacity(IBuffer buffer, out Span // Same as above for pinned arrays as well if (buffer is WindowsRuntimePinnedArrayBuffer pinnedArrayBuffer) { - span = pinnedArrayBuffer.GetSpanForCapacity(); + array = pinnedArrayBuffer.GetArray(out offset); return true; } - span = default; + array = null; + offset = 0; return false; } From 28706d2fdd96908ee461a8b9519001787e45b030 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 16:49:59 -0800 Subject: [PATCH 27/31] Remove unused usings in buffer extensions Remove unused using directives System.Runtime.CompilerServices and System.Runtime.InteropServices from WindowsRuntimeBufferExtensions.cs to clean up imports. No functional changes; reduces compiler warnings for unused namespaces. --- .../Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs index cffa8e0d6..e97677b4b 100644 --- a/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs +++ b/src/WinRT.Runtime2/Windows.Storage/Buffers/WindowsRuntimeBufferExtensions.cs @@ -5,8 +5,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using Windows.Storage.Streams; using WindowsRuntime; using WindowsRuntime.InteropServices; From 66dbe5d72f0a6c41f80adccf5d259d216a53b3b6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 17:29:17 -0800 Subject: [PATCH 28/31] Add Windows Buffers using; remove attribute Remove the special-case emission of the TypeMapAssociation assembly attribute from src/cswinrt/main.cpp. Add using Windows.Storage.Buffers and tidy up using directives in StreamOperationsImplementation.cs, WinRtIOHelper.cs and WinRtToNetFxStreamAdapter.cs (also adjust placement of System.Diagnostics.CodeAnalysis). These changes prepare the additions to use Windows.Storage.Buffers types and stop emitting the assembly-level TypeMapAssociation here. --- src/cswinrt/main.cpp | 10 ---------- .../StreamOperationsImplementation.cs | 5 ++--- .../additions/Windows.Storage.Streams/WinRtIOHelper.cs | 5 +++-- .../WinRtToNetFxStreamAdapter.cs | 4 +++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp index 78dbb3aee..fa2c18e86 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/StreamOperationsImplementation.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs index 7e283bccc..57d2372ed 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 diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs index e8356c67d..ccc98dd7c 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs @@ -7,10 +7,11 @@ 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 0cd3d46f6..80d692a10 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs @@ -6,6 +6,7 @@ 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; @@ -13,7 +14,8 @@ namespace Windows.Storage.Streams 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. /// From 989cb4c3d05df3ee295fbb1a37266a1fa21ab9f2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 17:55:30 -0800 Subject: [PATCH 29/31] Remove unused WindowsRuntime usings Remove the unused System.Runtime.InteropServices.WindowsRuntime using directives from WinRtIOHelper.cs and WinRtToNetFxStreamAdapter.cs. This is a tidy-up to eliminate redundant imports (and related compiler warnings); no functional changes. --- .../strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs | 1 - .../Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs index ccc98dd7c..62f90eb17 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtIOHelper.cs @@ -9,7 +9,6 @@ namespace Windows.Storage.Streams using global::System.IO; using global::System.Runtime.ExceptionServices; using global::System.Runtime.InteropServices; - using global::System.Runtime.InteropServices.WindowsRuntime; using Windows.Storage.Buffers; internal static class WinRtIOHelper diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs index 80d692a10..4ae9c8a65 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/WinRtToNetFxStreamAdapter.cs @@ -10,7 +10,6 @@ namespace Windows.Storage.Streams 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; From cbf1f96fe98f18649309d10de36ccf26adfb1ece Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 19:56:58 -0800 Subject: [PATCH 30/31] Stub WindowsRuntimeBuffer handling with TODOs Replace direct WindowsRuntimeBuffer cast and TryGetUnderlyingData usage with null/placeholder assignments and TODO comments in StreamOperationsImplementation.cs. This temporarily disables the optimized managed-array path (the buffer.TryGetUnderlyingData branch) and keeps the generic allocation fallback intact. Restore the original cast and TryGetUnderlyingData calls later to recover the optimized behavior. --- .../StreamOperationsImplementation.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs b/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs index 57d2372ed..a2201d007 100644 --- a/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs +++ b/src/cswinrt/strings/additions/Windows.Storage.Streams/StreamOperationsImplementation.cs @@ -81,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)); @@ -93,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; @@ -165,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) => { From 9da115cea1fa0713fa1e76dd75eab4d81ff671cd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 25 Feb 2026 22:24:33 -0800 Subject: [PATCH 31/31] Remove WindowsRuntime using; add Buffers namespace Remove obsolete System.Runtime.InteropServices.WindowsRuntime using directives from multiple test files and add using Windows.Storage.Buffers where needed. Affected files: FunctionalTests/Async/Program.cs, ObjectLifetimeTests/{App,MainWindow,MyControl,ObjectLifetimePage}.xaml.cs, UnitTest/ApiCompatTests.cs, and UnitTest/TestComponentCSharp_Tests.cs. This updates imports to use Windows.Storage.Buffers for buffer-related types and cleans up unnecessary WindowsRuntime interop using statements. --- src/Tests/FunctionalTests/Async/Program.cs | 2 +- src/Tests/ObjectLifetimeTests/App.xaml.cs | 1 - src/Tests/ObjectLifetimeTests/MainWindow.xaml.cs | 1 - src/Tests/ObjectLifetimeTests/MyControl.xaml.cs | 1 - src/Tests/ObjectLifetimeTests/ObjectLifetimePage.xaml.cs | 1 - src/Tests/UnitTest/ApiCompatTests.cs | 2 +- src/Tests/UnitTest/TestComponentCSharp_Tests.cs | 2 +- 7 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Tests/FunctionalTests/Async/Program.cs b/src/Tests/FunctionalTests/Async/Program.cs index b1e0933d0..5219984b4 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 de4880572..a543a935e 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 47bf9f206..5ebeb659a 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 fee8e8837..d26c04b97 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 753ee1d68..376c2b91c 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 036db3c3b..48fbdf0f2 100644 --- a/src/Tests/UnitTest/ApiCompatTests.cs +++ b/src/Tests/UnitTest/ApiCompatTests.cs @@ -22,11 +22,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 3c9962f25..fd7b472f8 100644 --- a/src/Tests/UnitTest/TestComponentCSharp_Tests.cs +++ b/src/Tests/UnitTest/TestComponentCSharp_Tests.cs @@ -12,7 +12,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Runtime.InteropServices.WindowsRuntime; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; @@ -34,6 +33,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;