diff --git a/Core/BkwdComp/SafeBuffer.PreNet40.cs b/Core/BkwdComp/SafeBuffer.PreNet40.cs new file mode 100644 index 00000000..f10bb219 --- /dev/null +++ b/Core/BkwdComp/SafeBuffer.PreNet40.cs @@ -0,0 +1,314 @@ +#if NET20 || NET35 +using Microsoft.Win32.SafeHandles; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; + +namespace System.Runtime.InteropServices +{ + /// + /// Provides a controlled memory buffer that can be used for reading and writing. Attempts to access memory outside the controlled buffer + /// (under-runs and overruns) raise exceptions. + /// + /// + [Security.SecurityCritical] + public abstract unsafe class SafeBuffer : SafeHandleZeroOrMinusOneIsInvalid + { + private static readonly UIntPtr Uninitialized = UIntPtr.Size == 4 ? (UIntPtr)uint.MaxValue : (UIntPtr)ulong.MaxValue; + + private UIntPtr numBytes; + + /// + /// + /// Creates a new instance of the class, and specifies whether the buffer handle is to be reliably released. + /// + /// + /// to reliably release the handle during the finalization phase; to prevent reliable + /// release (not recommended). + /// + protected SafeBuffer(bool ownsHandle) : base(ownsHandle) => numBytes = Uninitialized; + + /// Gets the size of the buffer, in bytes. + public ulong ByteLength + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get => numBytes != Uninitialized ? (ulong)numBytes : throw NotInitialized(); + } + + /// Obtains a pointer from a object for a block of memory. + /// + /// A byte pointer, passed by reference, to receive the pointer from within the object. You must set this + /// pointer to before you call this method. + /// + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public void AcquirePointer(ref byte* pointer) + { + if (numBytes == Uninitialized) + throw NotInitialized(); + + pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + var junk = false; + DangerousAddRef(ref junk); + pointer = (byte*)handle; + } + } + + /// + /// Defines the allocation size of the memory region in bytes. You must call this method before you use the SafeBuffer instance. + /// + /// The number of bytes in the buffer. + public void Initialize(ulong numBytes) + { + if (IntPtr.Size == 4 && numBytes > uint.MaxValue) + throw new ArgumentOutOfRangeException(nameof(numBytes), ResourceHelper.GetString("ArgumentOutOfRange_AddressSpace")); + Contract.EndContractBlock(); + + if (numBytes >= (ulong)Uninitialized) + throw new ArgumentOutOfRangeException(nameof(numBytes), ResourceHelper.GetString("ArgumentOutOfRange_UIntPtrMax-1")); + + this.numBytes = (UIntPtr)numBytes; + } + + /// + /// Specifies the allocation size of the memory buffer by using the specified number of elements and element size. You must call this + /// method before you use the SafeBuffer instance. + /// + /// The number of elements in the buffer. + /// The size of each element in the buffer. + public void Initialize(uint numElements, uint sizeOfEachElement) + { + if (IntPtr.Size == 4 && numElements * (ulong)sizeOfEachElement > uint.MaxValue) + throw new ArgumentOutOfRangeException(nameof(numElements), ResourceHelper.GetString("ArgumentOutOfRange_AddressSpace")); + Contract.EndContractBlock(); + + if (numElements * (ulong)sizeOfEachElement >= (ulong)Uninitialized) + throw new ArgumentOutOfRangeException(nameof(numElements), ResourceHelper.GetString("ArgumentOutOfRange_UIntPtrMax-1")); + + numBytes = checked((UIntPtr)(numElements * sizeOfEachElement)); + } + + /// + /// Defines the allocation size of the memory region by specifying the number of value types. You must call this method before you + /// use the SafeBuffer instance. + /// + /// The value type to allocate memory for. + /// The number of elements of the value type to allocate memory for. + public void Initialize(uint numElements) where T : struct => Initialize(numElements, (uint)Marshal.SizeOf(typeof(T))); + + /// Reads a value type from memory at the specified offset. + /// The value type to read. + /// The location from which to read the value type. You may have to consider alignment issues. + /// The value type that was read from memory. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public T Read(ulong byteOffset) where T : struct + { + if (numBytes == Uninitialized) + throw NotInitialized(); + + var sizeofT = (uint)Marshal.SizeOf(typeof(T)); + var ptr = (byte*)handle + byteOffset; + SpaceCheck(ptr, sizeofT); + + // return *(T*) (_ptr + byteOffset); + T value; + var mustCallRelease = false; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + DangerousAddRef(ref mustCallRelease); + GenericPtrToStructure(ptr, out value, sizeofT); + } + finally + { + if (mustCallRelease) + DangerousRelease(); + } + return value; + } + + /// + /// Reads the specified number of value types from memory starting at the offset, and writes them into an array starting at the index. + /// + /// The value type to read. + /// The location from which to start reading. + /// The output array to write to. + /// The location in the output array to begin writing to. + /// The number of value types to read from the input array and to write to the output array. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public void ReadArray(ulong byteOffset, T[] array, int index, int count) where T : struct + { + if (array == null) + throw new ArgumentNullException(nameof(array), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - index < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (numBytes == Uninitialized) + throw NotInitialized(); + + var sizeofT = (uint)Marshal.SizeOf(typeof(T)); + var alignedSizeofT = (uint)Marshal.SizeOf(typeof(T)); + var ptr = (byte*)handle + byteOffset; + SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count))); + + var mustCallRelease = false; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + DangerousAddRef(ref mustCallRelease); + + for (var i = 0; i < count; i++) + { + GenericPtrToStructure(ptr + alignedSizeofT * i, out array[i + index], sizeofT); + } + } + finally + { + if (mustCallRelease) + DangerousRelease(); + } + } + + /// Releases a pointer that was obtained by the method. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public void ReleasePointer() + { + if (numBytes == Uninitialized) + throw NotInitialized(); + + DangerousRelease(); + } + + /// Writes a value type to memory at the given location. + /// The value type to write. + /// The location at which to start writing. You may have to consider alignment issues. + /// The value to write. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public void Write(ulong byteOffset, T value) where T : struct + { + if (numBytes == Uninitialized) + throw NotInitialized(); + + var sizeofT = (uint)Marshal.SizeOf(typeof(T)); + var ptr = (byte*)handle + byteOffset; + SpaceCheck(ptr, sizeofT); + + // *((T*) (_ptr + byteOffset)) = value; + var mustCallRelease = false; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + DangerousAddRef(ref mustCallRelease); + GenericStructureToPtr(ref value, ptr, sizeofT); + } + finally + { + if (mustCallRelease) + DangerousRelease(); + } + } + + /// + /// Writes the specified number of value types to a memory location by reading bytes starting from the specified location in the + /// input array. + /// + /// The value type to write. + /// The location in memory to write to. + /// The input array. + /// The offset in the array to start reading from. + /// The number of value types to write. + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public void WriteArray(ulong byteOffset, T[] array, int index, int count) where T : struct + { + if (array == null) + throw new ArgumentNullException(nameof(array), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - index < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (numBytes == Uninitialized) + throw NotInitialized(); + + var sizeofT = (uint)Marshal.SizeOf(typeof(T)); + var alignedSizeofT = (uint)Marshal.SizeOf(typeof(T)); + var ptr = (byte*)handle + byteOffset; + SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count))); + + var mustCallRelease = false; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + DangerousAddRef(ref mustCallRelease); + for (var i = 0; i < count; i++) + { + GenericStructureToPtr(ref array[i + index], ptr + alignedSizeofT * i, sizeofT); + } + } + finally + { + if (mustCallRelease) + DangerousRelease(); + } + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal static void GenericPtrToStructure(byte* ptr, out T structure, uint sizeofT) where T : struct + { + structure = default; + PtrToStructureNative(ptr, __makeref(structure), sizeofT); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal static void GenericStructureToPtr(ref T structure, byte* ptr, uint sizeofT) where T : struct => StructureToPtrNative(__makeref(structure), ptr, sizeofT); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static void NotEnoughRoom() => throw new ArgumentException(ResourceHelper.GetString("Arg_BufferTooSmall")); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static InvalidOperationException NotInitialized() + { + Contract.Assert(false, "Uninitialized SafeBuffer! Someone needs to call Initialize before using this instance!"); + return new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_MustCallInitialize")); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + [ResourceExposure(ResourceScope.None)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static extern void PtrToStructureNative(byte* ptr, /*out T*/ TypedReference structure, uint sizeofT); + + [MethodImpl(MethodImplOptions.InternalCall)] + [ResourceExposure(ResourceScope.None)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static extern void StructureToPtrNative(/*ref T*/ TypedReference structure, byte* ptr, uint sizeofT); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private void SpaceCheck(byte* ptr, ulong sizeInBytes) + { + if ((ulong)numBytes < sizeInBytes) + NotEnoughRoom(); + if ((ulong)(ptr - (byte*)handle) > (ulong)numBytes - sizeInBytes) + NotEnoughRoom(); + } + } + + internal static class ResourceHelper + { + public static string GetString(string value, params object[] vars) => string.Format(Vanara.Properties.Resources.ResourceManager.GetString(value) ?? throw new InvalidOperationException(), vars); + } +} +#endif \ No newline at end of file diff --git a/Core/BkwdComp/UnmanagedMemoryAccessor.PreNet40.cs b/Core/BkwdComp/UnmanagedMemoryAccessor.PreNet40.cs new file mode 100644 index 00000000..2f3e41b6 --- /dev/null +++ b/Core/BkwdComp/UnmanagedMemoryAccessor.PreNet40.cs @@ -0,0 +1,794 @@ +#if NET20 || NET35 +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; + +namespace System.IO +{ + /// + /// Provides random access to unmanaged blocks of memory from managed code. + /// + public class UnmanagedMemoryAccessor : IDisposable + { + private FileAccess access; + [SecurityCritical] + private SafeBuffer buffer; + private bool canRead; + private bool canWrite; + private long offset; + + /// + /// Initializes a new instance of the class with a specified buffer, offset, and capacity. + /// + /// The buffer to contain the accessor. + /// The byte at which to start the accessor. + /// The size, in bytes, of memory to allocate. + [SecuritySafeCritical] + public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity) => Initialize(buffer, offset, capacity, FileAccess.Read); + + /// + /// Initializes a new instance of the class with a specified buffer, offset, capacity, and + /// access right. + /// + /// The buffer to contain the accessor. + /// The byte at which to start the accessor. + /// The size, in bytes, of memory to allocate. + /// The type of access allowed to the memory. The default is ReadWrite. + [SecuritySafeCritical] + public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity, FileAccess access) => Initialize(buffer, offset, capacity, access); + + /// Initializes a new instance of the class. + protected UnmanagedMemoryAccessor() => IsOpen = false; + + private unsafe delegate T AlignedPtrReadFunc(byte* ptr) where T : unmanaged; + + private unsafe delegate void AlignedPtrWriteFunc(byte* ptr); + + /// Determines whether the accessor is readable. + /// if the accessor is readable; otherwise, . + public bool CanRead => IsOpen && canRead; + + /// Determines whether the accessory is writable. + /// if the accessor is writable; otherwise, . + public bool CanWrite => IsOpen && canWrite; + + /// Gets the capacity of the accessor. + /// The capacity of the accessor. + [field: ContractPublicPropertyName("Capacity")] + public long Capacity { get; private set; } + + /// Determines whether the accessor is currently open by a process. + /// if the accessor is open; otherwise, . + protected bool IsOpen { get; private set; } + + /// Releases all resources used by the UnmanagedMemoryAccessor. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// Reads a structure of type from the accessor into a provided reference. + /// The type of structure. + /// The position in the accessor at which to begin reading. + /// The structure to contain the read data. + [SecurityCritical] + public void Read(long position, out T structure) where T : struct + { + if (position < 0) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + + if (!IsOpen) + throw new ObjectDisposedException("UnmanagedMemoryAccessor", ResourceHelper.GetString("ObjectDisposed_ViewAccessorClosed")); + if (!CanRead) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_Reading")); + + var sizeOfT = (uint)Marshal.SizeOf(typeof(T)); + if (position > Capacity - sizeOfT) + { + if (position >= Capacity) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + throw new ArgumentException(ResourceHelper.GetString("Argument_NotEnoughBytesToRead", typeof(T).FullName), nameof(position)); + } + + structure = buffer.Read((ulong)(offset + position)); + } + + /// Reads structures of type from the accessor into an array of type . + /// The type of structure. + /// The number of bytes in the accessor at which to begin reading. + /// The array to contain the structures read from the accessor. + /// The index in in which to place the first copied structure. + /// The number of structures of type to read from the accessor. + /// + /// The number of structures read into . This value can be less than if there are + /// fewer structures available, or zero if the end of the accessor is reached. + /// + [SecurityCritical] + public int ReadArray(long position, T[] array, int offset, int count) where T : struct + { + if (array == null) + throw new ArgumentNullException(nameof(array), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_OffsetAndLengthOutOfBounds")); + Contract.EndContractBlock(); + + if (!CanRead) + { + if (!IsOpen) + throw new ObjectDisposedException("UnmanagedMemoryAccessor", ResourceHelper.GetString("ObjectDisposed_ViewAccessorClosed")); + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_Reading")); + } + if (position < 0) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + + var sizeOfT = (uint)Marshal.SizeOf(typeof(T)); + + // only check position and ask for fewer Ts if count is too big + if (position >= Capacity) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + + var n = count; + var spaceLeft = Capacity - position; + if (spaceLeft < 0) + { + n = 0; + } + else + { + var spaceNeeded = (ulong)(sizeOfT * count); + if ((ulong)spaceLeft < spaceNeeded) + { + n = (int)(spaceLeft / sizeOfT); + } + } + + buffer.ReadArray((ulong)(this.offset + position), array, offset, n); + return n; + } + + /// Reads a Boolean value from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// or . + public bool ReadBoolean(long position) => ReadByte(position) != 0; + + /// Reads a byte value from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + public byte ReadByte(long position) + { + const int sizeOfType = sizeof(byte); + EnsureSafeToRead(position, sizeOfType); + + return InternalReadByte(position); + } + + /// Reads a character from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public char ReadChar(long position) { unsafe { return InternalRead(position, pointer => (char)(*pointer | *(pointer + 1) << 8)); } } + + /// Reads a decimal value from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public decimal ReadDecimal(long position) + { + const int sizeOfType = sizeof(decimal); + EnsureSafeToRead(position, sizeOfType); + + var decimalArray = new int[4]; + ReadArray(position, decimalArray, 0, decimalArray.Length); + return new decimal(decimalArray); + } + + /// Reads a double-precision floating-point value from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public double ReadDouble(long position) + { + unsafe + { + return InternalRead(position, pointer => + { + var lo = (uint) (*pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24); + var hi = (uint) (*(pointer + 4) | *(pointer + 5) << 8 | *(pointer + 6) << 16 | *(pointer + 7) << 24); + var tempResult = (ulong)hi << 32 | lo; + return *(double*) &tempResult; + }); + } + } + + /// Reads a 16-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public short ReadInt16(long position) { unsafe { return InternalRead(position, pointer => (short)(*pointer | *(pointer + 1) << 8)); } } + + /// Reads a 32-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public int ReadInt32(long position) { unsafe { return InternalRead(position, pointer => *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24); } } + + /// Reads a 64-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public long ReadInt64(long position) + { + unsafe + { + return InternalRead(position, pointer => + { + var lo = *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24; + var hi = *(pointer + 4) | *(pointer + 5) << 8 | *(pointer + 6) << 16 | *(pointer + 7) << 24; + return ((long)hi << 32) | (uint)lo; + }); + } + } + + /// Reads an 8-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public sbyte ReadSByte(long position) => unchecked((sbyte)ReadByte(position)); + + /// Reads a single-precision floating-point value from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public float ReadSingle(long position) + { + unsafe + { + return InternalRead(position, pointer => + { + var tempResult = (uint)(*pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24); + return *(float*)&tempResult; + }); + } + } + + /// Reads an unsigned 16-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public ushort ReadUInt16(long position) { unsafe { return InternalRead(position, pointer => (ushort)(*pointer | *(pointer + 1) << 8)); } } + + /// Reads an unsigned 32-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public uint ReadUInt32(long position) { unsafe { return InternalRead(position, pointer => (uint)(*pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24)); } } + + /// Reads an unsigned 64-bit integer from the accessor. + /// The number of bytes into the accessor at which to begin reading. + /// The value that was read. + [SecuritySafeCritical] + public ulong ReadUInt64(long position) + { + unsafe + { + return InternalRead(position, pointer => + { + var lo = (uint)(*pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24); + var hi = (uint)(*(pointer + 4) | *(pointer + 5) << 8 | *(pointer + 6) << 16 | *(pointer + 7) << 24); + return ((ulong)hi << 32) | lo; + }); + } + } + + /// Writes a Boolean value into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + public void Write(long position, bool value) => Write(position, (byte)(value ? 1 : 0)); + + /// Writes a byte value into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + public void Write(long position, byte value) + { + const int sizeOfType = sizeof(byte); + EnsureSafeToWrite(position, sizeOfType); + + InternalWriteByte(position, value); + } + + /// Writes a character into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, char value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + }); + } + } + + /// Writes a 16-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, short value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + }); + } + } + + /// Writes a 32-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, int value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + }); + } + } + + /// Writes a 64-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, long value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + *(pointer + 4) = (byte)(value >> 32); + *(pointer + 5) = (byte)(value >> 40); + *(pointer + 6) = (byte)(value >> 48); + *(pointer + 7) = (byte)(value >> 56); + }); + } + } + + /// Writes a decimal value into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, decimal value) + { + const int sizeOfType = sizeof(decimal); + EnsureSafeToWrite(position, sizeOfType); + + var decimalArray = new byte[16]; + GetDecimalBytes(value, decimalArray); + + var bits = new int[4]; + var flags = decimalArray[12] | (decimalArray[13] << 8) | (decimalArray[14] << 16) | (decimalArray[15] << 24); + var lo = decimalArray[0] | (decimalArray[1] << 8) | (decimalArray[2] << 16) | (decimalArray[3] << 24); + var mid = decimalArray[4] | (decimalArray[5] << 8) | (decimalArray[6] << 16) | (decimalArray[7] << 24); + var hi = decimalArray[8] | (decimalArray[9] << 8) | (decimalArray[10] << 16) | (decimalArray[11] << 24); + bits[0] = lo; + bits[1] = mid; + bits[2] = hi; + bits[3] = flags; + + WriteArray(position, bits, 0, bits.Length); + } + + /// Writes a Single into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, float value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + var lValue = value; + var tmpValue = *(uint*)&lValue; + *pointer = (byte)tmpValue; + *(pointer + 1) = (byte)(tmpValue >> 8); + *(pointer + 2) = (byte)(tmpValue >> 16); + *(pointer + 3) = (byte)(tmpValue >> 24); + }); + } + } + + /// Writes a Double into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, double value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + var lValue = value; + var tmpValue = *(ulong*)&lValue; + *pointer = (byte)tmpValue; + *(pointer + 1) = (byte)(tmpValue >> 8); + *(pointer + 2) = (byte)(tmpValue >> 16); + *(pointer + 3) = (byte)(tmpValue >> 24); + *(pointer + 4) = (byte)(tmpValue >> 32); + *(pointer + 5) = (byte)(tmpValue >> 40); + *(pointer + 6) = (byte)(tmpValue >> 48); + *(pointer + 7) = (byte)(tmpValue >> 56); + }); + } + } + + /// Writes an unsigned 8-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, sbyte value) => Write(position, unchecked((byte)value)); + + /// Writes an unsigned 16-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, ushort value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + }); + } + } + + /// Writes an unsigned 32-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, uint value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + }); + } + } + + /// Writes an unsigned 64-bit integer into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The value to write. + [SecuritySafeCritical] + public void Write(long position, ulong value) + { + unsafe + { + InternalWrite(position, value, pointer => + { + *pointer = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + *(pointer + 4) = (byte)(value >> 32); + *(pointer + 5) = (byte)(value >> 40); + *(pointer + 6) = (byte)(value >> 48); + *(pointer + 7) = (byte)(value >> 56); + }); + } + } + + /// Writes a structure into the accessor. + /// The number of bytes into the accessor at which to begin writing. + /// The structure to write. + [SecurityCritical] + public void Write(long position, ref T structure) where T : struct + { + if (position < 0) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + + if (!IsOpen) + throw new ObjectDisposedException("UnmanagedMemoryAccessor", ResourceHelper.GetString("ObjectDisposed_ViewAccessorClosed")); + if (!CanWrite) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_Writing")); + + var sizeOfT = (uint)Marshal.SizeOf(typeof(T)); + if (position > Capacity - sizeOfT) + { + if (position >= Capacity) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + throw new ArgumentException(ResourceHelper.GetString("Argument_NotEnoughBytesToWrite", typeof(T).FullName), nameof(position)); + } + + buffer.Write((ulong)(offset + position), structure); + } + + /// Writes structures from an array of type into the accessor. + /// The type of structure. + /// The number of bytes into the accessor at which to begin writing. + /// The array to write into the accessor. + /// The index in to start writing from. + /// The number of structures in to write. + [SecurityCritical] + public void WriteArray(long position, T[] array, int offset, int count) where T : struct + { + if (array == null) + throw new ArgumentNullException(nameof(array), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_OffsetAndLengthOutOfBounds")); + if (position < 0) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (position >= Capacity) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + Contract.EndContractBlock(); + + if (!IsOpen) + throw new ObjectDisposedException("UnmanagedMemoryAccessor", ResourceHelper.GetString("ObjectDisposed_ViewAccessorClosed")); + if (!CanWrite) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_Writing")); + + buffer.WriteArray((ulong)(this.offset + position), array, offset, count); + } + + internal static void GetDecimalBytes(decimal d, byte[] buffer) + { + Contract.Requires(buffer != null && buffer.Length >= 16, "[GetBytes]buffer != null && buffer.Length >= 16"); + Buffer.BlockCopy(decimal.GetBits(d), 0, buffer, 0, buffer.Length); + } + + /// Releases the unmanaged resources used by the UnmanagedMemoryAccessor and optionally releases the managed resources. + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) => IsOpen = false; + + /// Sets the initial values for the accessor. + /// The buffer to contain the accessor. + /// The byte at which to start the accessor. + /// The size, in bytes, of memory to allocate. + /// The type of access allowed to the memory. The default is ReadWrite. + [SecuritySafeCritical] + [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] + protected void Initialize(SafeBuffer buffer, long offset, long capacity, FileAccess access) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.ByteLength < (ulong)(offset + capacity)) + throw new ArgumentException(ResourceHelper.GetString("Argument_OffsetAndCapacityOutOfBounds")); + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException(nameof(access)); + Contract.EndContractBlock(); + + if (IsOpen) + throw new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_CalledTwice")); + + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + buffer.AcquirePointer(ref pointer); + if ((byte*)((long)pointer + offset + capacity) < pointer) + { + throw new ArgumentException(ResourceHelper.GetString("Argument_UnmanagedMemAccessorWrapAround")); + } + } + finally + { + if (pointer != null) + { + buffer.ReleasePointer(); + } + } + } + + this.offset = offset; + this.buffer = buffer; + this.Capacity = capacity; + this.access = access; + IsOpen = true; + canRead = (this.access & FileAccess.Read) != 0; + canWrite = (this.access & FileAccess.Write) != 0; + } + + private void EnsureSafeToRead(long position, int sizeOfType) + { + if (!IsOpen) + throw new ObjectDisposedException("UnmanagedMemoryAccessor", ResourceHelper.GetString("ObjectDisposed_ViewAccessorClosed")); + if (!CanRead) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_Reading")); + if (position < 0) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (position <= Capacity - sizeOfType) return; + if (position >= Capacity) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + throw new ArgumentException(ResourceHelper.GetString("Argument_NotEnoughBytesToRead"), nameof(position)); + } + + private void EnsureSafeToWrite(long position, int sizeOfType) + { + if (!IsOpen) + throw new ObjectDisposedException("UnmanagedMemoryAccessor", ResourceHelper.GetString("ObjectDisposed_ViewAccessorClosed")); + if (!CanWrite) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_Writing")); + if (position < 0) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (position > Capacity - sizeOfType) + { + if (position >= Capacity) + throw new ArgumentOutOfRangeException(nameof(position), ResourceHelper.GetString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + throw new ArgumentException(ResourceHelper.GetString("Argument_NotEnoughBytesToWrite", "Byte"), nameof(position)); + } + } + + private unsafe T InternalRead(long position, AlignedPtrReadFunc func) where T : unmanaged, IConvertible + { + var sizeOfType = sizeof(T); + EnsureSafeToRead(position, sizeOfType); + + T result; + + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + buffer.AcquirePointer(ref pointer); + pointer += offset + position; + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) + { +#endif + result = *(T*)pointer; +#if ALIGN_ACCESS + } + else + { + result = func(pointer); + } +#endif + } + finally + { + if (pointer != null) + { + buffer.ReleasePointer(); + } + } + + return result; + } + + [SecuritySafeCritical] + private byte InternalReadByte(long position) + { + Contract.Assert(CanRead, "UMA not readable"); + Contract.Assert(position >= 0, "position less than 0"); + Contract.Assert(position <= Capacity - sizeof(byte), "position is greater than capacity - sizeof(byte)"); + + byte result; + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + buffer.AcquirePointer(ref pointer); + result = *(pointer + offset + position); + } + finally + { + if (pointer != null) + { + buffer.ReleasePointer(); + } + } + } + return result; + } + + [SecuritySafeCritical] + private unsafe void InternalWrite(long position, T value, AlignedPtrWriteFunc func) where T : unmanaged, IConvertible + { + var sizeOfType = sizeof(T); + EnsureSafeToWrite(position, sizeOfType); + + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + buffer.AcquirePointer(ref pointer); + pointer += offset + position; +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) + { +#endif + *(T*)pointer = value; +#if ALIGN_ACCESS + } + else + { + func(pointer); + } +#endif + } + finally + { + if (pointer != null) + { + buffer.ReleasePointer(); + } + } + } + + [SecuritySafeCritical] + private void InternalWriteByte(long position, byte value) + { + Contract.Assert(CanWrite, "UMA not writable"); + Contract.Assert(position >= 0, "position less than 0"); + Contract.Assert(position <= Capacity - sizeof(byte), "position is greater than capacity - sizeof(byte)"); + + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + buffer.AcquirePointer(ref pointer); + *(pointer + offset + position) = value; + } + finally + { + if (pointer != null) + { + buffer.ReleasePointer(); + } + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Core/BkwdComp/UnmanagedMemoryStream.PreNet40.cs b/Core/BkwdComp/UnmanagedMemoryStream.PreNet40.cs new file mode 100644 index 00000000..0062ddcf --- /dev/null +++ b/Core/BkwdComp/UnmanagedMemoryStream.PreNet40.cs @@ -0,0 +1,818 @@ +#if NET20 || NET35 +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using System.Threading.Tasks; +using Vanara.Extensions; + +namespace System.IO +{ + /// Provides access to unmanaged blocks of memory from managed code. + /// + public class UnmanagedMemoryStream : Stream + { + internal bool _isOpen; + private FileAccess _access; + + [SecurityCritical] // auto-generated + private SafeBuffer _buffer; + + private long _capacity; + + [NonSerialized] + private Task _lastReadTask; + + private long _length; + + [SecurityCritical] + private unsafe byte* _mem; + + private long _offset; + private long _position; + + /// + /// Initializes a new instance of the class in a safe buffer with a specified offset and length.. + /// + /// The buffer to contain the unmanaged memory stream. + /// The byte position in the buffer at which to start the unmanaged memory stream. + /// The length of the unmanaged memory stream. + [SecuritySafeCritical] + public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length) => Initialize(buffer, offset, length, FileAccess.Read, false); + + /// + /// Initializes a new instance of the class in a safe buffer with a specified offset, length, and + /// file access. + /// + /// The buffer to contain the unmanaged memory stream. + /// The byte position in the buffer at which to start the unmanaged memory stream. + /// The length of the unmanaged memory stream. + /// The mode of file access to the unmanaged memory stream. + [SecuritySafeCritical] + public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access) => Initialize(buffer, offset, length, access, false); + + /// + /// Initializes a new instance of the class using the specified location and memory length. + /// + /// A pointer to an unmanaged memory location. + /// The length of the memory to use. + [SecurityCritical] + public unsafe UnmanagedMemoryStream(byte* pointer, long length) => Initialize(pointer, length, length, FileAccess.Read, false); + + /// + /// Initializes a new instance of the class using the specified location, memory length, total + /// amount of memory, and file access values. + /// + /// A pointer to an unmanaged memory location. + /// The length of the memory to use. + /// The total amount of memory assigned to the stream. + /// One of the values. + [SecurityCritical] + public unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access) => Initialize(pointer, length, capacity, access, false); + + [SecurityCritical] + internal UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) => Initialize(buffer, offset, length, access, skipSecurityCheck); + + [SecurityCritical] + internal unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck) => Initialize(pointer, length, capacity, access, skipSecurityCheck); + + /// Initializes a new instance of the class. + [SecuritySafeCritical] + protected UnmanagedMemoryStream() + { + unsafe + { + _mem = null; + } + _isOpen = false; + } + + /// Gets a value indicating whether a stream supports reading. + public override bool CanRead + { + [Pure] + get => _isOpen && (_access & FileAccess.Read) != 0; + } + + /// Gets a value indicating whether a stream supports seeking. + public override bool CanSeek + { + [Pure] + get => _isOpen; + } + + /// Gets a value indicating whether a stream supports writing. + public override bool CanWrite + { + [Pure] + get => _isOpen && (_access & FileAccess.Write) != 0; + } + + /// Gets the stream length (size) or the total amount of memory assigned to a stream (capacity). + /// The size or capacity of the stream. + public long Capacity + { + get + { + if (!_isOpen) ErrorStreamIsClosed(); + return _capacity; + } + } + + /// Gets the length of the data in a stream. + /// The length of the data in the stream. + public override long Length + { + get + { + if (!_isOpen) ErrorStreamIsClosed(); + return Interlocked.Read(ref _length); + } + } + + /// Gets or sets the position within the current stream. + public override long Position + { + get + { + if (!CanSeek) ErrorStreamIsClosed(); + Contract.EndContractBlock(); + return Interlocked.Read(ref _position); + } + [SecuritySafeCritical] + set + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (!CanSeek) ErrorStreamIsClosed(); + + unsafe + { + // On 32 bit machines, ensure we don't wrap around. + if (value > int.MaxValue || _mem + value < _mem) + throw new ArgumentOutOfRangeException(nameof(value), ResourceHelper.GetString("ArgumentOutOfRange_StreamLength")); + } + + Interlocked.Exchange(ref _position, value); + } + } + + /// Gets or sets a byte pointer to a stream based on the current position in the stream. + /// A byte pointer. + public unsafe byte* PositionPointer + { + [SecurityCritical] // auto-generated_required + get + { + if (_buffer != null) + { + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer")); + } + + // Use a temp to avoid a race + var pos = Interlocked.Read(ref _position); + if (pos > _capacity) + throw new IndexOutOfRangeException(ResourceHelper.GetString("IndexOutOfRange_UMSPosition")); + var ptr = _mem + pos; + if (!_isOpen) ErrorStreamIsClosed(); + return ptr; + } + [SecurityCritical] // auto-generated_required + set + { + if (_buffer != null) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer")); + if (!_isOpen) ErrorStreamIsClosed(); + + if (value < _mem) + throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin")); + + Interlocked.Exchange(ref _position, value - _mem); + } + } + + internal unsafe byte* Pointer + { + [SecurityCritical] + get + { + if (_buffer != null) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer")); + + return _mem; + } + } + + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + public override void Flush() + { + if (!_isOpen) ErrorStreamIsClosed(); + } + + /// + /// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors + /// cancellation requests. + /// + /// The token to monitor for cancellation requests. The default value is None. + /// A task that represents the asynchronous flush operation. + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public virtual Task FlushAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try + { + Flush(); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + /// + /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream + /// by the number of bytes read. + /// + /// + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between + /// and ( + - 1) replaced by the bytes read from the + /// current source. + /// + /// + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// + /// The maximum number of bytes to be read from the current stream. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not + /// currently available, or zero (0) if the end of the stream has been reached. + /// + [SecuritySafeCritical] + public override int Read([In, Out] byte[] buffer, int offset, int count) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // Keep this in sync with contract validation in ReadAsync + + if (!_isOpen) ErrorStreamIsClosed(); + if (!CanRead) ErrorReadNotSupported(); + + // Use a local variable to avoid a race where another thread changes our position after we decide we can read some bytes. + var pos = Interlocked.Read(ref _position); + var len = Interlocked.Read(ref _length); + var n = len - pos; + if (n > count) + n = count; + if (n <= 0) + return 0; + + var nInt = (int)n; // Safe because n <= count, which is an Int32 + if (nInt < 0) + nInt = 0; // _position could be beyond EOF + Contract.Assert(pos + nInt >= 0, "_position + n >= 0"); // len is less than 2^63 -1. + + if (_buffer != null) + { + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + _buffer.AcquirePointer(ref pointer); + BufferMemcpy(buffer, offset, pointer + pos + _offset, 0, nInt); + } + finally + { + if (pointer != null) + { + _buffer.ReleasePointer(); + } + } + } + } + else + { + unsafe + { + BufferMemcpy(buffer, offset, _mem + pos, 0, nInt); + } + } + Interlocked.Exchange(ref _position, pos + n); + return nInt; + } + + /// + /// Asynchronously reads a sequence of bytes from the current stream and advances the position within the stream by the number of + /// bytes read. + /// + /// The buffer to write the data into. + /// The byte offset in buffer at which to begin writing data from the stream. + /// The maximum number of bytes to read. + /// The token to monitor for cancellation requests. The default value is None. + /// + /// A task that represents the asynchronous read operation. The value of the TResult parameter contains the total number of bytes + /// read into the buffer. The result value can be less than the number of bytes requested if the number of bytes currently available + /// is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached. + /// + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public virtual Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // contract validation copied from Read(...) + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try + { + var n = Read(buffer, offset, count); + var t = _lastReadTask; + return t != null && t.Result == n ? t : _lastReadTask = Task.FromResult(n); + } + catch (Exception ex) + { + Contract.Assert(!(ex is OperationCanceledException)); + return Task.FromException(ex); + } + } + + /// + /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. + /// + /// The unsigned byte cast to an Int32, or -1 if at the end of the stream. + [SecuritySafeCritical] + public override int ReadByte() + { + if (!_isOpen) ErrorStreamIsClosed(); + if (!CanRead) ErrorReadNotSupported(); + + var pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition + var len = Interlocked.Read(ref _length); + if (pos >= len) + return -1; + Interlocked.Exchange(ref _position, pos + 1); + int result; + if (_buffer != null) + { + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + _buffer.AcquirePointer(ref pointer); + result = *(pointer + pos + _offset); + } + finally + { + if (pointer != null) + { + _buffer.ReleasePointer(); + } + } + } + } + else + { + unsafe + { + result = _mem[pos]; + } + } + return result; + } + + /// When overridden in a derived class, sets the position within the current stream. + /// A byte offset relative to the origin parameter. + /// A value of type SeekOrigin indicating the reference point used to obtain the new position. + /// The new position within the current stream. + public override long Seek(long offset, SeekOrigin loc) + { + if (!_isOpen) ErrorStreamIsClosed(); + switch (loc) + { + case SeekOrigin.Begin: + if (offset < 0) + throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin")); + Interlocked.Exchange(ref _position, offset); + break; + + case SeekOrigin.Current: + var pos = Interlocked.Read(ref _position); + if (offset + pos < 0) + throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin")); + Interlocked.Exchange(ref _position, offset + pos); + break; + + case SeekOrigin.End: + var len = Interlocked.Read(ref _length); + if (len + offset < 0) + throw new IOException(ResourceHelper.GetString("IO.IO_SeekBeforeBegin")); + Interlocked.Exchange(ref _position, len + offset); + break; + + default: + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidSeekOrigin")); + } + + var finalPos = Interlocked.Read(ref _position); + Contract.Assert(finalPos >= 0, "_position >= 0"); + return finalPos; + } + + /// When overridden in a derived class, sets the length of the current stream. + /// The desired length of the current stream in bytes. + [SecuritySafeCritical] + public override void SetLength(long value) + { + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_buffer != null) + throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UmsSafeBuffer")); + if (!_isOpen) ErrorStreamIsClosed(); + if (!CanWrite) ErrorWriteNotSupported(); + + if (value > _capacity) + throw new IOException(ResourceHelper.GetString("IO.IO_FixedCapacity")); + + var pos = Interlocked.Read(ref _position); + var len = Interlocked.Read(ref _length); + if (value > len) + { + unsafe + { + BufferZeroMemory(_mem + len, value - len); + } + } + Interlocked.Exchange(ref _length, value); + if (pos > value) + { + Interlocked.Exchange(ref _position, value); + } + } + + /// + /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within + /// this stream by the number of bytes written. + /// + /// + /// An array of bytes. This method copies bytes from to the current stream. + /// + /// + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// + /// The number of bytes to be written to the current stream. + [SecuritySafeCritical] + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // Keep contract validation in sync with WriteAsync(..) + + if (!_isOpen) ErrorStreamIsClosed(); + if (!CanWrite) ErrorWriteNotSupported(); + + var pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition + var len = Interlocked.Read(ref _length); + var n = pos + count; + // Check for overflow + if (n < 0) + throw new IOException(ResourceHelper.GetString("IO.IO_StreamTooLong")); + + if (n > _capacity) + { + throw new NotSupportedException(ResourceHelper.GetString("IO.IO_FixedCapacity")); + } + + if (_buffer == null) + { + // Check to see whether we are now expanding the stream and must zero any memory in the middle. + if (pos > len) + { + unsafe + { + BufferZeroMemory(_mem + len, pos - len); + } + } + + // set length after zeroing memory to avoid race condition of accessing unzeroed memory + if (n > len) + { + Interlocked.Exchange(ref _length, n); + } + } + + if (_buffer != null) + { + var bytesLeft = _capacity - pos; + if (bytesLeft < count) + { + throw new ArgumentException(ResourceHelper.GetString("Arg_BufferTooSmall")); + } + + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + _buffer.AcquirePointer(ref pointer); + BufferMemcpy(pointer + pos + _offset, 0, buffer, offset, count); + } + finally + { + if (pointer != null) + { + _buffer.ReleasePointer(); + } + } + } + } + else + { + unsafe + { + BufferMemcpy(_mem + pos, 0, buffer, offset, count); + } + } + Interlocked.Exchange(ref _position, n); + } + + /// + /// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number + /// of bytes written, and monitors cancellation requests. + /// + /// The buffer to write data from. + /// The zero-based byte offset in buffer from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + /// The token to monitor for cancellation requests. The default value is None. + /// A task that represents the asynchronous write operation. + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public virtual Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer), ResourceHelper.GetString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // contract validation copied from Write(..) + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try + { + Write(buffer, offset, count); + return Task.CompletedTask; + } + catch (Exception ex) + { + Contract.Assert(!(ex is OperationCanceledException)); + return Task.FromException(ex); + } + } + + /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. + /// The byte to write to the stream. + [SecuritySafeCritical] + public override void WriteByte(byte value) + { + if (!_isOpen) ErrorStreamIsClosed(); + if (!CanWrite) ErrorWriteNotSupported(); + + var pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition + var len = Interlocked.Read(ref _length); + var n = pos + 1; + if (pos >= len) + { + // Check for overflow + if (n < 0) + throw new IOException(ResourceHelper.GetString("IO.IO_StreamTooLong")); + + if (n > _capacity) + throw new NotSupportedException(ResourceHelper.GetString("IO.IO_FixedCapacity")); + + // Check to see whether we are now expanding the stream and must zero any memory in the middle. don't do if created from SafeBuffer + if (_buffer == null) + { + if (pos > len) + { + unsafe + { + BufferZeroMemory(_mem + len, pos - len); + } + } + + // set length after zeroing memory to avoid race condition of accessing unzeroed memory + Interlocked.Exchange(ref _length, n); + } + } + + if (_buffer != null) + { + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + _buffer.AcquirePointer(ref pointer); + *(pointer + pos + _offset) = value; + } + finally + { + if (pointer != null) + { + _buffer.ReleasePointer(); + } + } + } + } + else + { + unsafe + { + _mem[pos] = value; + } + } + Interlocked.Exchange(ref _position, n); + } + + [SecurityCritical] + internal void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.ByteLength < (ulong)(offset + length)) + throw new ArgumentException(ResourceHelper.GetString("Argument_InvalidSafeBufferOffLen")); + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException(nameof(access)); + Contract.EndContractBlock(); + + if (_isOpen) + throw new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_CalledTwice")); + if (!skipSecurityCheck) +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); +#pragma warning restore 618 + + // check for wraparound + unsafe + { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + buffer.AcquirePointer(ref pointer); + if (pointer + offset + length < pointer) + { + throw new ArgumentException(ResourceHelper.GetString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround")); + } + } + finally + { + if (pointer != null) + { + buffer.ReleasePointer(); + } + } + } + + _offset = offset; + _buffer = buffer; + _length = length; + _capacity = length; + _access = access; + _isOpen = true; + } + + [SecurityCritical] + internal unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck) + { + if (pointer == null) + throw new ArgumentNullException(nameof(pointer)); + if (length < 0 || capacity < 0) + throw new ArgumentOutOfRangeException(length < 0 ? "length" : "capacity", ResourceHelper.GetString("ArgumentOutOfRange_NeedNonNegNum")); + if (length > capacity) + throw new ArgumentOutOfRangeException(nameof(length), ResourceHelper.GetString("ArgumentOutOfRange_LengthGreaterThanCapacity")); + Contract.EndContractBlock(); + // Check for wraparound. + if ((byte*)((long)pointer + capacity) < pointer) + throw new ArgumentOutOfRangeException(nameof(capacity), ResourceHelper.GetString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround")); + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException(nameof(access), ResourceHelper.GetString("ArgumentOutOfRange_Enum")); + if (_isOpen) + throw new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_CalledTwice")); + + if (!skipSecurityCheck) +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); +#pragma warning restore 618 + + _mem = pointer; + _offset = 0; + _length = length; + _capacity = capacity; + _access = access; + _isOpen = true; + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + [SecuritySafeCritical] + protected override void Dispose(bool disposing) + { + _isOpen = false; + unsafe { _mem = null; } + + // Stream allocates WaitHandles for async calls. So for correctness call base.Dispose(disposing) for better perf, avoiding + // waiting for the finalizers to run on those types. + base.Dispose(disposing); + } + + /// + /// Initializes a new instance of the UnmanagedMemoryStream class in a safe buffer with a specified offset, length, and file access. + /// + /// The buffer to contain the unmanaged memory stream. + /// The byte position in the buffer at which to start the unmanaged memory stream. + /// The length of the unmanaged memory stream. + /// The mode of file access to the unmanaged memory stream. + [SecuritySafeCritical] + protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access) => Initialize(buffer, offset, length, access, false); + + /// Initializes a new instance of the UnmanagedMemoryStream class by using a pointer to an unmanaged memory location. + /// A pointer to an unmanaged memory location. + /// The length of the memory to use. + /// The total amount of memory assigned to the stream. + /// One of the values. + [SecurityCritical] + protected unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access) => Initialize(pointer, length, capacity, access, false); + + [SecurityCritical] + private static unsafe void BufferMemcpy(byte[] dest, int destIndex, byte* src, int srcIndex, int len) + { + Contract.Assert(srcIndex >= 0 && destIndex >= 0 && len >= 0, "Index and length must be non-negative!"); + Contract.Assert(dest.Length - destIndex >= len, "not enough bytes in dest"); + if (len == 0) + return; + Marshal.Copy((IntPtr)src, dest, srcIndex, len); + } + + [SecurityCritical] + private static unsafe void BufferMemcpy(byte* dest, int destIndex, byte[] src, int srcIndex, int len) + { + Contract.Assert(srcIndex >= 0 && destIndex >= 0 && len >= 0, "Index and length must be non-negative!"); + Contract.Assert(src.Length - srcIndex >= len, "not enough bytes in src"); + if (len == 0) + return; + Marshal.Copy(src, srcIndex, (IntPtr)dest, len); + } + + private static unsafe void BufferZeroMemory(byte* src, long len) => ((IntPtr)src).FillMemory(0, len); + + private static void ErrorReadNotSupported() => throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UnreadableStream")); + + private static void ErrorStreamIsClosed() => throw new ObjectDisposedException(null, ResourceHelper.GetString("ObjectDisposed_StreamClosed")); + + private static void ErrorWriteNotSupported() => throw new NotSupportedException(ResourceHelper.GetString("NotSupported_UnwritableStream")); + } +} +#endif \ No newline at end of file