#if NET20 || NET35 using Microsoft.Win32.SafeHandles; using System.Linq; using System.Runtime.ConstrainedExecution; using Vanara.Extensions; 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; 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")); 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) => Initialize((ulong)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, AlignedSizeOf()); /// 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 => handle.ToStructure(numBytes.ToUInt64(), (int)byteOffset); /// /// 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")); if (numBytes == Uninitialized) throw NotInitialized(); Array.Copy(handle.ToArray(count, (int)byteOffset, numBytes.ToUInt64()), 0, array, index, count); } /// 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(); handle.Write(value, (int)byteOffset, numBytes.ToUInt64()); } /// /// 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")); if (numBytes == Uninitialized) throw NotInitialized(); handle.Write(array, (int)byteOffset, numBytes.ToUInt64()); } [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() => new InvalidOperationException(ResourceHelper.GetString("InvalidOperation_MustCallInitialize")); internal static uint AlignedSizeOf() { var size = SizeOf(); return (size == 1 || size == 2) ? size : (uint)((size + 3) & (~3)); } internal static uint SizeOf() => (uint)Marshal.SizeOf(typeof(T)); } 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