using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Vanara.Extensions;
namespace Vanara.InteropServices
{
///
/// A safe unmanaged array of structures allocated on the global heap. Array size determined by allocated memory size divided by size of structure.
///
/// The type of the array elements.
public class SafeNativeArray : SafeNativeArrayBase where TElem : struct
{
/// Initializes a new instance of the class.
public SafeNativeArray() : base(0, 0) { }
/// Initializes a new instance of the class from a copy of a managed TElem array.
/// The array of bytes to copy.
/// The number of bytes to allocate in front of the array allocation.
public SafeNativeArray(TElem[] array, uint headerSize = 0) : base(array, headerSize) { }
/// Initializes a new instance of the class.
/// The element count. This value can be 0.
/// The number of bytes to allocate in front of the array allocation.
public SafeNativeArray(int elementCount, uint headerSize = 0) : base((uint)(ElemSize * elementCount), headerSize) { }
/// Initializes a new instance of the class.
/// The handle.
/// The size of memory allocated to the handle, in bytes.
/// if set to true if this class is responsible for freeing the memory on disposal.
public SafeNativeArray(IntPtr ptr, int size, bool ownsHandle) : base(ptr, (uint)size, ownsHandle) { }
/// Initializes a new instance of the class.
/// The handle.
/// The size of memory allocated to the handle, in bytes.
/// if set to true if this class is responsible for freeing the memory on disposal.
///
/// The number of bytes allocated in front of the array allocation.
///
/// Sets the exact number of elements in the array.
///
/// If set to true the array is read-only and will throw an exception if an attempt is made to add, insert or remove elements.
///
public SafeNativeArray(IntPtr ptr, uint size, bool ownsHandle, uint headerSize, int elementCount, bool readOnly) :
base(ptr, size, ownsHandle, headerSize, elementCount, readOnly)
{
}
/// Creates a new instance of the class using a byte count.
/// The number of bytes to allocate for this new array.
public static SafeNativeArray FromByteCount(int byteCount)
{
if (byteCount % ElemSize != 0) throw new ArgumentException($"{nameof(byteCount)} parameter must be a multiple of the size of structure {nameof(TElem)}.");
return new SafeNativeArray(byteCount / ElemSize);
}
}
/// A safe unmanaged array of structures. Array size determined by size of structure.
/// The type of the array elements.
/// The type of the memory allocation functions to use.
public class SafeNativeArrayBase : SafeMemoryHandle, IList where TElem : struct where TMem : IMemoryMethods, new()
{
/// Gets the size of the element.
protected static readonly int ElemSize = Marshal.SizeOf(typeof(TElem));
/// Initializes a new instance of the class from a copy of a managed TElem array.
/// The array of with which to initialize the .
///
/// The number of bytes to allocate in front of the array allocation. This may be used to write the element count or other values
/// using the event.
///
public SafeNativeArrayBase(TElem[] array, uint headerSize = 0) : this((uint)GetRequiredSize(array?.Length ?? 0, headerSize), headerSize) => Elements = array;
/// Initializes a new instance of the class.
/// The handle.
/// The size of memory allocated to the handle, in bytes.
/// if set to true if this class is responsible for freeing the memory on disposal.
///
/// The number of bytes to allocate in front of the array allocation. This may be used to write the element count or other values
/// using the event.
///
public SafeNativeArrayBase(IntPtr ptr, uint size, bool ownsHandle = true, uint headerSize = 0) : base(ptr, (int)size, ownsHandle) { HeaderSize = headerSize; Count = GetElemCountFromBytes(size, headerSize); OnUpdateHeader(); }
/// Initializes a new instance of the class.
/// The handle.
/// The size of memory allocated to the handle, in bytes.
/// if set to true if this class is responsible for freeing the memory on disposal.
///
/// The number of bytes to allocate in front of the array allocation. This may be used to write the element count or other values
/// using the event.
///
/// Sets the exact number of elements in the array.
///
/// If set to true the array is read-only and will throw an exception if an attempt is made to add, insert or remove elements.
///
public SafeNativeArrayBase(IntPtr ptr, uint size, bool ownsHandle, uint headerSize, int elementCount, bool readOnly) :
base(ptr, (int)size, ownsHandle)
{
HeaderSize = headerSize;
Count = elementCount;
IsReadOnly = readOnly;
OnUpdateHeader();
}
/// Initializes a new instance of the class.
/// The number of bytes to allocate for this new array.
///
/// The number of bytes to allocate in front of the array allocation. This may be used to write the element count or other values
/// using the event.
///
protected SafeNativeArrayBase(uint byteCount, uint headerSize = 0) : base((int)byteCount) { HeaderSize = headerSize; Count = GetElemCountFromBytes(byteCount, headerSize); OnUpdateHeader(); }
/// Gets the number of elements contained in the .
public virtual int Count { get; protected set; }
/// Gets the size, in bytes, of the header.
/// The size of the header.
public uint HeaderSize { get; }
/// Gets a value indicating whether the is read-only.
public bool IsReadOnly { get; } = false;
/// Gets or sets the elements.
/// The elements of the array.
protected virtual TElem[] Elements
{
get => handle.ToArray(Count, (int)HeaderSize);
set
{
if (IsReadOnly) throw new InvalidOperationException("Array is read-only.");
var len = value?.Length ?? 0;
Count = len;
Size = GetRequiredSize(len, HeaderSize);
Zero();
handle.Write(value, (int)HeaderSize);
OnCountChanged();
OnUpdateHeader();
}
}
/// Called when the count has changed.
protected virtual void OnCountChanged() { }
/// Called when the header needs to be refreshed.
protected virtual void OnUpdateHeader() { }
/// Gets or sets the value at the specified index.
/// The value.
/// The index.
///
/// index or index
public virtual TElem this[int index]
{
get => handle.ToStructure(Size, ElemOffset(index));
set
{
if (IsReadOnly)
throw new InvalidOperationException("Array is read-only.");
handle.Write(value, ElemOffset(index), Size);
}
}
/// Adds an item to the .
/// The object to add to the .
public void Add(TElem item) => Insert(Count, item);
/// Removes all items from the .
public virtual void Clear()
{
if (IsReadOnly)
throw new InvalidOperationException("Array is read-only.");
Size = (int)HeaderSize;
Count = 0;
OnCountChanged();
}
/// Determines whether this instance contains the object.
/// The object to locate in the .
///
/// true if is found in the ; otherwise, false.
///
public bool Contains(TElem item) => EnumElements().Contains(item);
///
/// Copies the elements of the to an , starting
/// at a particular index.
///
///
/// The one-dimensional that is the destination of the elements copied from
/// . The must have zero-based indexing.
///
/// The zero-based index in at which copying begins.
public void CopyTo(TElem[] array, int arrayIndex) => Elements.CopyTo(array, arrayIndex);
/// Returns an enumerator that iterates through the collection.
/// A that can be used to iterate through the collection.
public IEnumerator GetEnumerator() => EnumElements().GetEnumerator();
/// Gets an array of pointers to each element in this native array.
/// An array of pointers to each element in this native array.
public IntPtr[] GetPointers() => Enumerable.Range(0, Count - 1).Select(i => PtrOfElem(i)).ToArray();
/// Gets a reference to the structure at a specified index.
/// The index.
/// The reference to the value.
public ref TElem GetRefAt(int index) => ref handle.AsRef(ElemOffset(index), Size);
/// Determines the index of a specific item in the .
/// The object to locate in the .
/// The index of if found in the list; otherwise, -1.
public int IndexOf(TElem item) => Array.IndexOf(Elements, item);
/// Inserts an item to the at the specified index.
/// The zero-based index at which should be inserted.
/// The object to insert into the .
/// index
public virtual void Insert(int index, TElem item)
{
if (index < 0 || index > Count) throw new ArgumentOutOfRangeException(nameof(index));
if (IsReadOnly) throw new InvalidOperationException("Array is read-only.");
var newSz = GetRequiredSize(++Count, HeaderSize);
var newPtr = mm.AllocMem(newSz);
var insertPt = ElemOffset(index);
if (index > 0)
CopyTo(newPtr, 0, insertPt);
newPtr.Write(item, insertPt, newSz);
if (index < Count - 1)
CopyTo(newPtr.Offset(insertPt + ElemSize), insertPt, Size - insertPt);
mm.FreeMem(handle);
SetHandle(newPtr);
sz = newSz;
OnCountChanged();
}
/// Removes the first occurrence of a specific object from the .
/// The object to remove from the .
///
/// true if was successfully removed from the ;
/// otherwise, false. This method also returns false if is not found in the original .
///
public bool Remove(TElem item)
{
var idx = IndexOf(item);
if (idx == -1) return false;
RemoveAt(idx);
return true;
}
/// Removes the item at the specified index.
/// The zero-based index of the item to remove.
/// index
public virtual void RemoveAt(int index)
{
if (IsReadOnly) throw new InvalidOperationException("Array is read-only.");
var rmvPt = ElemOffset(index);
Count--;
var newSz = GetRequiredSize(Count, HeaderSize);
CopyTo(handle.Offset(rmvPt), rmvPt + ElemSize, newSz - rmvPt);
Size -= ElemSize;
OnCountChanged();
}
/// Returns an enumerator that iterates through a collection.
/// An object that can be used to iterate through the collection.
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// Copies the values of this array to another native memory pointer.
/// The PTR.
/// The start.
/// The length.
///
protected void CopyTo(IntPtr ptr, int start = 0, int length = -1)
{
if (start > Size) throw new ArgumentOutOfRangeException();
if (length == -1) length = Size - start;
if (length + start > Size || length + start < 0) throw new ArgumentOutOfRangeException();
handle.CopyTo(start, ptr, length);
}
/// Enumerates the elements.
/// An enumeration of values from the pointer.
protected IEnumerable EnumElements() => handle.ToIEnum(Count, (int)HeaderSize);
private static int GetElemCountFromBytes(uint byteSize, uint headerSize)
{
if (headerSize > byteSize) throw new ArgumentOutOfRangeException(nameof(byteSize));
if ((byteSize - headerSize) % ElemSize != 0) throw new ArgumentOutOfRangeException(nameof(byteSize));
return (int)Convert.ChangeType((byteSize - headerSize) / ElemSize, typeof(int));
}
private static int GetRequiredSize(int elementCount, uint headerSize) => ElemSize * elementCount + (int)headerSize;
private int ElemOffset(int index) => index >= 0 && index < Count ? index * ElemSize + (int)HeaderSize : throw new ArgumentOutOfRangeException(nameof(index));
private IntPtr PtrOfElem(int index) => handle.Offset(ElemOffset(index));
}
}