using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Vanara.Extensions;
using Vanara.PInvoke;
namespace Vanara.InteropServices
{
/// A memory block aligned on a specific byte boundary.
/// The type of the memory.
///
public class AlignedMemory : SafeAllocatedMemoryHandle where TMem : IMemoryMethods, new()
{
/// The implementation instance.
protected TMem mm = new TMem();
/// The number of bytes currently allocated.
protected SizeT sz;
private int alignment;
private IntPtr rawMemPtr;
/// Initializes a new instance of the class.
/// The number of aligned bytes to allocate.
/// The memory offset to which the memory is aligned.
///
/// sizeInBytes - The value of this argument must be non-negative or alignmentBoundary - Alignment must be a power of 2.
///
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();
}
/// Initializes a new instance of the class.
/// The number of aligned bytes to allocate.
/// The type to which to align the memory. Memory will be aligned to the byte size of this type.
public AlignedMemory(int sizeInBytes, Type alignemntType) : this(sizeInBytes, Marshal.SizeOf(alignemntType))
{
}
/// Gets a value indicating whether the handle value is invalid.
public override bool IsInvalid => handle == IntPtr.Zero;
/// Gets or sets the size in bytes of the allocated memory block.
/// The size in bytes of the allocated memory block.
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;
}
}
}
/// When overridden in a derived class, executes the code required to free the handle.
///
/// if the handle is released successfully; otherwise, in the event of a catastrophic failure,
/// . In this case, it generates a releaseHandleFailed MDA Managed Debugging Assistant.
///
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;
}
}