Added backwards compatible classes SafeBuffer, UnmanagedMemoryAccessor and UnmanagedMemoryStream for .NET 2.0 and 3.5.

pull/60/head
David Hall 2019-03-25 17:35:51 -07:00
parent f64c2b5e6b
commit 40155dc239
3 changed files with 1926 additions and 0 deletions

View File

@ -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
{
/// <summary>
/// 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.
/// </summary>
/// <seealso cref="Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid"/>
[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;
/// <inheritdoc />
/// <summary>
/// Creates a new instance of the <see cref="T:System.Runtime.InteropServices.SafeBuffer" /> class, and specifies whether the buffer handle is to be reliably released.
/// </summary>
/// <param name="ownsHandle">
/// <see langword="true" /> to reliably release the handle during the finalization phase; <see langword="false" /> to prevent reliable
/// release (not recommended).
/// </param>
protected SafeBuffer(bool ownsHandle) : base(ownsHandle) => numBytes = Uninitialized;
/// <summary>Gets the size of the buffer, in bytes.</summary>
public ulong ByteLength
{
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
get => numBytes != Uninitialized ? (ulong)numBytes : throw NotInitialized();
}
/// <summary>Obtains a pointer from a <see cref="SafeBuffer"/> object for a block of memory.</summary>
/// <param name="pointer">
/// A byte pointer, passed by reference, to receive the pointer from within the <see cref="SafeBuffer"/> object. You must set this
/// pointer to <see langword="null"/> before you call this method.
/// </param>
[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;
}
}
/// <summary>
/// Defines the allocation size of the memory region in bytes. You must call this method before you use the SafeBuffer instance.
/// </summary>
/// <param name="numBytes">The number of bytes in the buffer.</param>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="numElements">The number of elements in the buffer.</param>
/// <param name="sizeOfEachElement">The size of each element in the buffer.</param>
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));
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The value type to allocate memory for.</typeparam>
/// <param name="numElements">The number of elements of the value type to allocate memory for.</param>
public void Initialize<T>(uint numElements) where T : struct => Initialize(numElements, (uint)Marshal.SizeOf(typeof(T)));
/// <summary>Reads a value type from memory at the specified offset.</summary>
/// <typeparam name="T">The value type to read.</typeparam>
/// <param name="byteOffset">The location from which to read the value type. You may have to consider alignment issues.</param>
/// <returns>The value type that was read from memory.</returns>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public T Read<T>(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;
}
/// <summary>
/// Reads the specified number of value types from memory starting at the offset, and writes them into an array starting at the index.
/// </summary>
/// <typeparam name="T">The value type to read.</typeparam>
/// <param name="byteOffset">The location from which to start reading.</param>
/// <param name="array">The output array to write to.</param>
/// <param name="index">The location in the output array to begin writing to.</param>
/// <param name="count">The number of value types to read from the input array and to write to the output array.</param>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public void ReadArray<T>(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();
}
}
/// <summary>Releases a pointer that was obtained by the <see cref="AcquirePointer(ref byte*)"/> method.</summary>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public void ReleasePointer()
{
if (numBytes == Uninitialized)
throw NotInitialized();
DangerousRelease();
}
/// <summary>Writes a value type to memory at the given location.</summary>
/// <typeparam name="T">The value type to write.</typeparam>
/// <param name="byteOffset">The location at which to start writing. You may have to consider alignment issues.</param>
/// <param name="value">The value to write.</param>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public void Write<T>(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();
}
}
/// <summary>
/// Writes the specified number of value types to a memory location by reading bytes starting from the specified location in the
/// input array.
/// </summary>
/// <typeparam name="T">The value type to write.</typeparam>
/// <param name="byteOffset">The location in memory to write to.</param>
/// <param name="array">The input array.</param>
/// <param name="index">The offset in the array to start reading from.</param>
/// <param name="count">The number of value types to write.</param>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public void WriteArray<T>(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<T>(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<T>(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

View File

@ -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
{
/// <inheritdoc />
/// <summary>Provides random access to unmanaged blocks of memory from managed code.</summary>
/// <seealso cref="T:System.IDisposable" />
public class UnmanagedMemoryAccessor : IDisposable
{
private FileAccess access;
[SecurityCritical]
private SafeBuffer buffer;
private bool canRead;
private bool canWrite;
private long offset;
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryAccessor"/> class with a specified buffer, offset, and capacity.
/// </summary>
/// <param name="buffer">The buffer to contain the accessor.</param>
/// <param name="offset">The byte at which to start the accessor.</param>
/// <param name="capacity">The size, in bytes, of memory to allocate.</param>
[SecuritySafeCritical]
public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity) => Initialize(buffer, offset, capacity, FileAccess.Read);
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryAccessor"/> class with a specified buffer, offset, capacity, and
/// access right.
/// </summary>
/// <param name="buffer">The buffer to contain the accessor.</param>
/// <param name="offset">The byte at which to start the accessor.</param>
/// <param name="capacity">The size, in bytes, of memory to allocate.</param>
/// <param name="access">The type of access allowed to the memory. The default is ReadWrite.</param>
[SecuritySafeCritical]
public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity, FileAccess access) => Initialize(buffer, offset, capacity, access);
/// <summary>Initializes a new instance of the <see cref="UnmanagedMemoryAccessor"/> class.</summary>
protected UnmanagedMemoryAccessor() => IsOpen = false;
private unsafe delegate T AlignedPtrReadFunc<out T>(byte* ptr) where T : unmanaged;
private unsafe delegate void AlignedPtrWriteFunc(byte* ptr);
/// <summary>Determines whether the accessor is readable.</summary>
/// <value><see langword="true"/> if the accessor is readable; otherwise, <see langword="false"/>.</value>
public bool CanRead => IsOpen && canRead;
/// <summary>Determines whether the accessory is writable.</summary>
/// <value><see langword="true"/> if the accessor is writable; otherwise, <see langword="false"/>.</value>
public bool CanWrite => IsOpen && canWrite;
/// <summary>Gets the capacity of the accessor.</summary>
/// <value>The capacity of the accessor.</value>
[field: ContractPublicPropertyName("Capacity")]
public long Capacity { get; private set; }
/// <summary>Determines whether the accessor is currently open by a process.</summary>
/// <value><see langword="true"/> if the accessor is open; otherwise, <see langword="false"/>.</value>
protected bool IsOpen { get; private set; }
/// <summary>Releases all resources used by the UnmanagedMemoryAccessor.</summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>Reads a structure of type <typeparamref name="T"/> from the accessor into a provided reference.</summary>
/// <typeparam name="T">The type of structure.</typeparam>
/// <param name="position">The position in the accessor at which to begin reading.</param>
/// <param name="structure">The structure to contain the read data.</param>
[SecurityCritical]
public void Read<T>(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<T>((ulong)(offset + position));
}
/// <summary>Reads structures of type <typeparamref name="T"/> from the accessor into an array of type <typeparamref name="T"/>.</summary>
/// <typeparam name="T">The type of structure.</typeparam>
/// <param name="position">The number of bytes in the accessor at which to begin reading.</param>
/// <param name="array">The array to contain the structures read from the accessor.</param>
/// <param name="offset">The index in <paramref name="array"/> in which to place the first copied structure.</param>
/// <param name="count">The number of structures of type <typeparamref name="T"/> to read from the accessor.</param>
/// <returns>
/// The number of structures read into <paramref name="array"/>. This value can be less than <paramref name="count"/> if there are
/// fewer structures available, or zero if the end of the accessor is reached.
/// </returns>
[SecurityCritical]
public int ReadArray<T>(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;
}
/// <summary>Reads a Boolean value from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns><see langword="true"/> or <see langword="false"/>.</returns>
public bool ReadBoolean(long position) => ReadByte(position) != 0;
/// <summary>Reads a byte value from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
public byte ReadByte(long position)
{
const int sizeOfType = sizeof(byte);
EnsureSafeToRead(position, sizeOfType);
return InternalReadByte(position);
}
/// <summary>Reads a character from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[SecuritySafeCritical]
public char ReadChar(long position) { unsafe { return InternalRead(position, pointer => (char)(*pointer | *(pointer + 1) << 8)); } }
/// <summary>Reads a decimal value from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[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);
}
/// <summary>Reads a double-precision floating-point value from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[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;
});
}
}
/// <summary>Reads a 16-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[SecuritySafeCritical]
public short ReadInt16(long position) { unsafe { return InternalRead(position, pointer => (short)(*pointer | *(pointer + 1) << 8)); } }
/// <summary>Reads a 32-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[SecuritySafeCritical]
public int ReadInt32(long position) { unsafe { return InternalRead(position, pointer => *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24); } }
/// <summary>Reads a 64-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[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;
});
}
}
/// <summary>Reads an 8-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[SecuritySafeCritical]
public sbyte ReadSByte(long position) => unchecked((sbyte)ReadByte(position));
/// <summary>Reads a single-precision floating-point value from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[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;
});
}
}
/// <summary>Reads an unsigned 16-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[SecuritySafeCritical]
public ushort ReadUInt16(long position) { unsafe { return InternalRead(position, pointer => (ushort)(*pointer | *(pointer + 1) << 8)); } }
/// <summary>Reads an unsigned 32-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[SecuritySafeCritical]
public uint ReadUInt32(long position) { unsafe { return InternalRead(position, pointer => (uint)(*pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24)); } }
/// <summary>Reads an unsigned 64-bit integer from the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin reading.</param>
/// <returns>The value that was read.</returns>
[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;
});
}
}
/// <summary>Writes a Boolean value into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
public void Write(long position, bool value) => Write(position, (byte)(value ? 1 : 0));
/// <summary>Writes a byte value into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
public void Write(long position, byte value)
{
const int sizeOfType = sizeof(byte);
EnsureSafeToWrite(position, sizeOfType);
InternalWriteByte(position, value);
}
/// <summary>Writes a character into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[SecuritySafeCritical]
public void Write(long position, char value)
{
unsafe
{
InternalWrite(position, value, pointer =>
{
*pointer = (byte)value;
*(pointer + 1) = (byte)(value >> 8);
});
}
}
/// <summary>Writes a 16-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[SecuritySafeCritical]
public void Write(long position, short value)
{
unsafe
{
InternalWrite(position, value, pointer =>
{
*pointer = (byte)value;
*(pointer + 1) = (byte)(value >> 8);
});
}
}
/// <summary>Writes a 32-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
});
}
}
/// <summary>Writes a 64-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
});
}
}
/// <summary>Writes a decimal value into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
}
/// <summary>Writes a Single into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
});
}
}
/// <summary>Writes a Double into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
});
}
}
/// <summary>Writes an unsigned 8-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[SecuritySafeCritical]
public void Write(long position, sbyte value) => Write(position, unchecked((byte)value));
/// <summary>Writes an unsigned 16-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[SecuritySafeCritical]
public void Write(long position, ushort value)
{
unsafe
{
InternalWrite(position, value, pointer =>
{
*pointer = (byte)value;
*(pointer + 1) = (byte)(value >> 8);
});
}
}
/// <summary>Writes an unsigned 32-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
});
}
}
/// <summary>Writes an unsigned 64-bit integer into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="value">The value to write.</param>
[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);
});
}
}
/// <summary>Writes a structure into the accessor.</summary>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="structure">The structure to write.</param>
[SecurityCritical]
public void Write<T>(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);
}
/// <summary>Writes structures from an array of type <typeparamref name="T"/> into the accessor.</summary>
/// <typeparam name="T">The type of structure.</typeparam>
/// <param name="position">The number of bytes into the accessor at which to begin writing.</param>
/// <param name="array">The array to write into the accessor.</param>
/// <param name="offset">The index in <paramref name="array"/> to start writing from.</param>
/// <param name="count">The number of structures in <paramref name="array"/> to write.</param>
[SecurityCritical]
public void WriteArray<T>(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);
}
/// <summary>Releases the unmanaged resources used by the UnmanagedMemoryAccessor and optionally releases the managed resources.</summary>
/// <param name="disposing">
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing) => IsOpen = false;
/// <summary>Sets the initial values for the accessor.</summary>
/// <param name="buffer">The buffer to contain the accessor.</param>
/// <param name="offset">The byte at which to start the accessor.</param>
/// <param name="capacity">The size, in bytes, of memory to allocate.</param>
/// <param name="access">The type of access allowed to the memory. The default is ReadWrite.</param>
[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<T>(long position, AlignedPtrReadFunc<T> 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<T>(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

View File

@ -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
{
/// <summary>Provides access to unmanaged blocks of memory from managed code.</summary>
/// <seealso cref="System.IO.Stream"/>
public class UnmanagedMemoryStream : Stream
{
internal bool _isOpen;
private FileAccess _access;
[SecurityCritical] // auto-generated
private SafeBuffer _buffer;
private long _capacity;
[NonSerialized]
private Task<int> _lastReadTask;
private long _length;
[SecurityCritical]
private unsafe byte* _mem;
private long _offset;
private long _position;
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class in a safe buffer with a specified offset and length..
/// </summary>
/// <param name="buffer">The buffer to contain the unmanaged memory stream.</param>
/// <param name="offset">The byte position in the buffer at which to start the unmanaged memory stream.</param>
/// <param name="length">The length of the unmanaged memory stream.</param>
[SecuritySafeCritical]
public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length) => Initialize(buffer, offset, length, FileAccess.Read, false);
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class in a safe buffer with a specified offset, length, and
/// file access.
/// </summary>
/// <param name="buffer">The buffer to contain the unmanaged memory stream.</param>
/// <param name="offset">The byte position in the buffer at which to start the unmanaged memory stream.</param>
/// <param name="length">The length of the unmanaged memory stream.</param>
/// <param name="access">The mode of file access to the unmanaged memory stream.</param>
[SecuritySafeCritical]
public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access) => Initialize(buffer, offset, length, access, false);
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class using the specified location and memory length.
/// </summary>
/// <param name="pointer">A pointer to an unmanaged memory location.</param>
/// <param name="length">The length of the memory to use.</param>
[SecurityCritical]
public unsafe UnmanagedMemoryStream(byte* pointer, long length) => Initialize(pointer, length, length, FileAccess.Read, false);
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class using the specified location, memory length, total
/// amount of memory, and file access values.
/// </summary>
/// <param name="pointer">A pointer to an unmanaged memory location.</param>
/// <param name="length">The length of the memory to use.</param>
/// <param name="capacity">The total amount of memory assigned to the stream.</param>
/// <param name="access">One of the <see cref="FileAccess"/> values.</param>
[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);
/// <summary>Initializes a new instance of the <see cref="UnmanagedMemoryStream"/> class.</summary>
[SecuritySafeCritical]
protected UnmanagedMemoryStream()
{
unsafe
{
_mem = null;
}
_isOpen = false;
}
/// <summary>Gets a value indicating whether a stream supports reading.</summary>
public override bool CanRead
{
[Pure]
get => _isOpen && (_access & FileAccess.Read) != 0;
}
/// <summary>Gets a value indicating whether a stream supports seeking.</summary>
public override bool CanSeek
{
[Pure]
get => _isOpen;
}
/// <summary>Gets a value indicating whether a stream supports writing.</summary>
public override bool CanWrite
{
[Pure]
get => _isOpen && (_access & FileAccess.Write) != 0;
}
/// <summary>Gets the stream length (size) or the total amount of memory assigned to a stream (capacity).</summary>
/// <value>The size or capacity of the stream.</value>
public long Capacity
{
get
{
if (!_isOpen) ErrorStreamIsClosed();
return _capacity;
}
}
/// <summary>Gets the length of the data in a stream.</summary>
/// <value>The length of the data in the stream.</value>
public override long Length
{
get
{
if (!_isOpen) ErrorStreamIsClosed();
return Interlocked.Read(ref _length);
}
}
/// <summary>Gets or sets the position within the current stream.</summary>
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);
}
}
/// <summary>Gets or sets a byte pointer to a stream based on the current position in the stream.</summary>
/// <value>A byte pointer.</value>
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;
}
}
/// <summary>Clears all buffers for this stream and causes any buffered data to be written to the underlying device.</summary>
public override void Flush()
{
if (!_isOpen) ErrorStreamIsClosed();
}
/// <summary>
/// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors
/// cancellation requests.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.</param>
/// <returns>A task that represents the asynchronous flush operation.</returns>
[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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="buffer">
/// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between
/// <paramref name="offset"/> and ( <paramref name="offset"/> + <paramref name="count"/> - 1) replaced by the bytes read from the
/// current source.
/// </param>
/// <param name="offset">
/// The zero-based byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.
/// </param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// 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.
/// </returns>
[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;
}
/// <summary>
/// Asynchronously reads a sequence of bytes from the current stream and advances the position within the stream by the number of
/// bytes read.
/// </summary>
/// <param name="buffer">The buffer to write the data into.</param>
/// <param name="offset">The byte offset in buffer at which to begin writing data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.</param>
/// <returns>
/// 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.
/// </returns>
[HostProtection(ExternalThreading = true)]
[ComVisible(false)]
public virtual Task<int> 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<int>(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<int>(ex);
}
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
[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;
}
/// <summary>When overridden in a derived class, sets the position within the current stream.</summary>
/// <param name="offset">A byte offset relative to the origin parameter.</param>
/// <param name="loc">A value of type SeekOrigin indicating the reference point used to obtain the new position.</param>
/// <returns>The new position within the current stream.</returns>
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;
}
/// <summary>When overridden in a derived class, sets the length of the current stream.</summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
[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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="buffer">
/// An array of bytes. This method copies <paramref name="count"/> bytes from <paramref name="buffer"/> to the current stream.
/// </param>
/// <param name="offset">
/// The zero-based byte offset in <paramref name="buffer"/> at which to begin copying bytes to the current stream.
/// </param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
[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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The zero-based byte offset in buffer from which to begin copying bytes to the stream.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is None.</param>
/// <returns>A task that represents the asynchronous write operation.</returns>
[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<int>(ex);
}
}
/// <summary>Writes a byte to the current position in the stream and advances the position within the stream by one byte.</summary>
/// <param name="value">The byte to write to the stream.</param>
[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;
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
[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);
}
/// <summary>
/// Initializes a new instance of the UnmanagedMemoryStream class in a safe buffer with a specified offset, length, and file access.
/// </summary>
/// <param name="buffer">The buffer to contain the unmanaged memory stream.</param>
/// <param name="offset">The byte position in the buffer at which to start the unmanaged memory stream.</param>
/// <param name="length">The length of the unmanaged memory stream.</param>
/// <param name="access">The mode of file access to the unmanaged memory stream.</param>
[SecuritySafeCritical]
protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access) => Initialize(buffer, offset, length, access, false);
/// <summary>Initializes a new instance of the UnmanagedMemoryStream class by using a pointer to an unmanaged memory location.</summary>
/// <param name="pointer">A pointer to an unmanaged memory location.</param>
/// <param name="length">The length of the memory to use.</param>
/// <param name="capacity">The total amount of memory assigned to the stream.</param>
/// <param name="access">One of the <see cref="FileAccess"/> values.</param>
[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