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)); } }