Vanara/Core/InteropServices/AlignedMemory.cs

97 lines
3.8 KiB
C#

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Vanara.Extensions;
using Vanara.PInvoke;
namespace Vanara.InteropServices
{
/// <summary>A memory block aligned on a specific byte boundary.</summary>
/// <typeparam name="TMem">The type of the memory.</typeparam>
/// <seealso cref="Vanara.InteropServices.SafeAllocatedMemoryHandle"/>
public class AlignedMemory<TMem> : SafeAllocatedMemoryHandle where TMem : IMemoryMethods, new()
{
/// <summary>The <see cref="IMemoryMethods"/> implementation instance.</summary>
protected TMem mm = new TMem();
/// <summary>The number of bytes currently allocated.</summary>
protected SizeT sz;
private int alignment;
private IntPtr rawMemPtr;
/// <summary>Initializes a new instance of the <see cref="AlignedMemory{TMem}"/> class.</summary>
/// <param name="sizeInBytes">The number of aligned bytes to allocate.</param>
/// <param name="alignmentBoundary">The memory offset to which the memory is aligned.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// sizeInBytes - The value of this argument must be non-negative or alignmentBoundary - Alignment must be a power of 2.
/// </exception>
public AlignedMemory(int sizeInBytes, int alignmentBoundary) : base(IntPtr.Zero, true)
{
if (sizeInBytes < 0)
throw new ArgumentOutOfRangeException(nameof(sizeInBytes), "The value of this argument must be non-negative");
if (!IsPowerOfTwo(alignmentBoundary))
throw new ArgumentOutOfRangeException(nameof(alignmentBoundary), "Alignment must be a power of 2.");
if (sizeInBytes == 0) return;
RuntimeHelpers.PrepareConstrainedRegions();
alignment = alignmentBoundary;
// TODO: Look at optimizing allocation by checking to see if first one is already aligned.
rawMemPtr = mm.AllocMem(sz = GetBufSz(sizeInBytes));
SetHandle(rawMemPtr.Offset(GetOffset()));
Zero();
}
/// <summary>Initializes a new instance of the <see cref="AlignedMemory{TMem}"/> class.</summary>
/// <param name="sizeInBytes">The number of aligned bytes to allocate.</param>
/// <param name="alignemntType">The type to which to align the memory. Memory will be aligned to the byte size of this type.</param>
public AlignedMemory(int sizeInBytes, Type alignemntType) : this(sizeInBytes, Marshal.SizeOf(alignemntType))
{
}
/// <summary>Gets a value indicating whether the handle value is invalid.</summary>
public override bool IsInvalid => handle == IntPtr.Zero;
/// <summary>Gets or sets the size in bytes of the allocated memory block.</summary>
/// <value>The size in bytes of the allocated memory block.</value>
public override SizeT Size
{
get => IsInvalid ? 0 : sz + 1 - alignment;
set
{
if (value == 0)
{
ReleaseHandle();
}
else
{
if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
var newsz = GetBufSz(value);
rawMemPtr = IsInvalid ? mm.AllocMem(newsz) : mm.ReAllocMem(rawMemPtr, newsz);
SetHandle(rawMemPtr.Offset(GetOffset()));
if (value > Size)
handle.Offset(Size).FillMemory(0, value - Size);
sz = newsz;
}
}
}
/// <summary>When overridden in a derived class, executes the code required to free the handle.</summary>
/// <returns>
/// <see langword="true"/> if the handle is released successfully; otherwise, in the event of a catastrophic failure,
/// <see langword="false"/>. In this case, it generates a releaseHandleFailed MDA Managed Debugging Assistant.
/// </returns>
protected override bool ReleaseHandle()
{
mm.FreeMem(rawMemPtr);
sz = 0;
handle = IntPtr.Zero;
return true;
}
private bool IsPowerOfTwo(int x) => x != 0 && (x & (x - 1)) == 0;
private int GetBufSz(int value) => value + alignment - 1;
private long GetOffset() => alignment - rawMemPtr.ToInt64() % alignment;
}
}