2018-10-26 14:24:07 -04:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Linq ;
using System.Runtime.InteropServices ;
using Vanara.Extensions ;
namespace Vanara.InteropServices
{
/// <summary>
/// A safe unmanaged array of structures allocated on the global heap. Array size determined by allocated memory size divided by size of structure.
/// </summary>
2019-04-24 10:27:31 -04:00
/// <typeparam name="TElem">The type of the array elements.</typeparam>
2019-05-08 11:30:06 -04:00
public class SafeNativeArray < TElem > : SafeNativeArrayBase < TElem , HGlobalMemoryMethods > where TElem : struct
2018-10-26 14:24:07 -04:00
{
2018-11-28 14:33:55 -05:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArray{TElem}"/> class.</summary>
2019-05-08 11:30:06 -04:00
public SafeNativeArray ( ) : base ( 0 , 0 ) { }
2018-10-26 14:24:07 -04:00
2019-04-17 14:25:05 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArray{TElem}"/> class from a copy of a managed TElem array.</summary>
2018-10-26 14:24:07 -04:00
/// <param name="array">The array of bytes to copy.</param>
2019-09-30 22:36:28 -04:00
/// <param name="headerSize">The number of bytes to allocate in front of the array allocation.</param>
public SafeNativeArray ( TElem [ ] array , uint headerSize = 0 ) : base ( array , headerSize ) { }
2018-10-26 14:24:07 -04:00
2018-11-28 14:33:55 -05:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArray{TElem}"/> class.</summary>
2018-10-26 14:24:07 -04:00
/// <param name="elementCount">The element count. This value can be 0.</param>
2019-09-30 22:36:28 -04:00
/// <param name="headerSize">The number of bytes to allocate in front of the array allocation.</param>
public SafeNativeArray ( int elementCount , uint headerSize = 0 ) : base ( ( uint ) ( ElemSize * elementCount ) , headerSize ) { }
2019-05-08 11:30:06 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArray{TElem}"/> class.</summary>
/// <param name="ptr">The handle.</param>
/// <param name="size">The size of memory allocated to the handle, in bytes.</param>
/// <param name="ownsHandle">if set to <c>true</c> if this class is responsible for freeing the memory on disposal.</param>
public SafeNativeArray ( IntPtr ptr , int size , bool ownsHandle ) : base ( ptr , ( uint ) size , ownsHandle ) { }
2019-07-03 16:45:15 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArray{TElem}"/> class.</summary>
/// <param name="ptr">The handle.</param>
/// <param name="size">The size of memory allocated to the handle, in bytes.</param>
/// <param name="ownsHandle">if set to <c>true</c> if this class is responsible for freeing the memory on disposal.</param>
/// <param name="headerSize">
/// The number of bytes allocated in front of the array allocation.
/// </param>
/// <param name="elementCount">Sets the exact number of elements in the array.</param>
/// <param name="readOnly">
/// If set to <c>true</c> the array is read-only and will throw an exception if an attempt is made to add, insert or remove elements.
/// </param>
public SafeNativeArray ( IntPtr ptr , uint size , bool ownsHandle , uint headerSize , int elementCount , bool readOnly ) :
base ( ptr , size , ownsHandle , headerSize , elementCount , readOnly )
{
}
2019-05-08 11:30:06 -04:00
/// <summary>Creates a new instance of the <see cref="SafeNativeArray{TElem}"/> class using a byte count.</summary>
/// <param name="byteCount">The number of bytes to allocate for this new array.</param>
public static SafeNativeArray < TElem > 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 < TElem > ( byteCount / ElemSize ) ;
}
}
/// <summary>A safe unmanaged array of structures. Array size determined by size of structure.</summary>
/// <typeparam name="TElem">The type of the array elements.</typeparam>
/// <typeparam name="TMem">The type of the memory allocation functions to use.</typeparam>
public class SafeNativeArrayBase < TElem , TMem > : SafeMemoryHandle < TMem > , IList < TElem > where TElem : struct where TMem : IMemoryMethods , new ( )
{
/// <summary>Gets the size of the element.</summary>
protected static readonly int ElemSize = Marshal . SizeOf ( typeof ( TElem ) ) ;
2019-07-03 16:45:15 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArrayBase{TElem, TMem}"/> class from a copy of a managed TElem array.</summary>
2019-05-08 11:30:06 -04:00
/// <param name="array">The array of <typeparamref name="TElem"/> with which to initialize the <see cref="SafeNativeArrayBase{TElem, TMem}"/>.</param>
/// <param name="headerSize">
/// 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 <see cref="OnUpdateHeader"/> event.
/// </param>
public SafeNativeArrayBase ( TElem [ ] array , uint headerSize = 0 ) : this ( ( uint ) GetRequiredSize ( array ? . Length ? ? 0 , headerSize ) , headerSize ) = > Elements = array ;
2018-10-26 14:24:07 -04:00
2019-07-03 16:45:15 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArrayBase{TElem, TMem}"/> class.</summary>
2019-04-24 10:27:31 -04:00
/// <param name="ptr">The handle.</param>
/// <param name="size">The size of memory allocated to the handle, in bytes.</param>
/// <param name="ownsHandle">if set to <c>true</c> if this class is responsible for freeing the memory on disposal.</param>
2019-05-08 11:30:06 -04:00
/// <param name="headerSize">
/// 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 <see cref="OnUpdateHeader"/> event.
/// </param>
public SafeNativeArrayBase ( IntPtr ptr , uint size , bool ownsHandle = true , uint headerSize = 0 ) : base ( ptr , ( int ) size , ownsHandle ) { HeaderSize = headerSize ; Count = GetElemCountFromBytes ( size , headerSize ) ; OnUpdateHeader ( ) ; }
2018-10-26 14:24:07 -04:00
2019-07-03 16:45:15 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArrayBase{TElem, TMem}"/> class.</summary>
/// <param name="ptr">The handle.</param>
/// <param name="size">The size of memory allocated to the handle, in bytes.</param>
/// <param name="ownsHandle">if set to <c>true</c> if this class is responsible for freeing the memory on disposal.</param>
/// <param name="headerSize">
/// 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 <see cref="OnUpdateHeader"/> event.
/// </param>
/// <param name="elementCount">Sets the exact number of elements in the array.</param>
/// <param name="readOnly">
/// If set to <c>true</c> the array is read-only and will throw an exception if an attempt is made to add, insert or remove elements.
/// </param>
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 ( ) ;
}
2019-04-24 10:27:31 -04:00
/// <summary>Initializes a new instance of the <see cref="SafeNativeArray{TElem}"/> class.</summary>
/// <param name="byteCount">The number of bytes to allocate for this new array.</param>
2019-05-08 11:30:06 -04:00
/// <param name="headerSize">
/// 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 <see cref="OnUpdateHeader"/> event.
/// </param>
protected SafeNativeArrayBase ( uint byteCount , uint headerSize = 0 ) : base ( ( int ) byteCount ) { HeaderSize = headerSize ; Count = GetElemCountFromBytes ( byteCount , headerSize ) ; OnUpdateHeader ( ) ; }
2019-04-24 10:27:31 -04:00
2018-11-28 14:33:55 -05:00
/// <summary>Gets the number of elements contained in the <see cref="SafeNativeArray{TElem}"/>.</summary>
2019-05-08 11:30:06 -04:00
public virtual int Count { get ; protected set ; }
/// <summary>Gets the size, in bytes, of the header.</summary>
/// <value>The size of the header.</value>
public uint HeaderSize { get ; }
2018-10-26 14:24:07 -04:00
2019-04-17 14:25:05 -04:00
/// <summary>Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</summary>
2019-07-03 16:45:15 -04:00
public bool IsReadOnly { get ; } = false ;
2018-10-26 14:24:07 -04:00
/// <summary>Gets or sets the elements.</summary>
/// <value>The elements of the array.</value>
2019-08-22 15:57:03 -04:00
protected virtual TElem [ ] Elements
2018-10-26 14:24:07 -04:00
{
2019-05-08 11:30:06 -04:00
get = > handle . ToArray < TElem > ( Count , ( int ) HeaderSize ) ;
2018-10-26 14:24:07 -04:00
set
{
2019-07-03 16:45:15 -04:00
if ( IsReadOnly ) throw new InvalidOperationException ( "Array is read-only." ) ;
2019-05-09 17:56:31 -04:00
var len = value ? . Length ? ? 0 ;
Count = len ;
Size = GetRequiredSize ( len , HeaderSize ) ;
Zero ( ) ;
2019-08-17 23:13:57 -04:00
handle . Write ( value , ( int ) HeaderSize ) ;
2019-05-08 11:30:06 -04:00
OnCountChanged ( ) ;
OnUpdateHeader ( ) ;
2018-10-26 14:24:07 -04:00
}
}
2019-05-08 11:30:06 -04:00
/// <summary>Called when the count has changed.</summary>
protected virtual void OnCountChanged ( ) { }
/// <summary>Called when the header needs to be refreshed.</summary>
protected virtual void OnUpdateHeader ( ) { }
2019-04-17 14:51:11 -04:00
/// <summary>Gets or sets the <typeparamref name="TElem"/> value at the specified index.</summary>
/// <value>The <typeparamref name="TElem"/> value.</value>
2019-04-17 14:25:05 -04:00
/// <param name="index">The index.</param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException">index or index</exception>
2019-08-22 15:57:03 -04:00
public virtual TElem this [ int index ]
2018-10-26 14:24:07 -04:00
{
2019-11-19 14:52:44 -05:00
get = > handle . ToStructure < TElem > ( Size , ElemOffset ( index ) ) ;
2019-07-03 16:45:15 -04:00
set
{
if ( IsReadOnly )
throw new InvalidOperationException ( "Array is read-only." ) ;
2019-11-19 14:52:44 -05:00
handle . Write ( value , ElemOffset ( index ) , Size ) ;
2019-07-03 16:45:15 -04:00
}
2018-10-26 14:24:07 -04:00
}
2019-04-17 14:25:05 -04:00
/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
2018-10-26 14:24:07 -04:00
public void Add ( TElem item ) = > Insert ( Count , item ) ;
2019-04-17 14:25:05 -04:00
/// <summary>Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
2019-05-08 11:30:06 -04:00
public virtual void Clear ( )
{
2019-07-03 16:45:15 -04:00
if ( IsReadOnly )
throw new InvalidOperationException ( "Array is read-only." ) ;
2019-05-08 11:30:06 -04:00
Size = ( int ) HeaderSize ;
Count = 0 ;
OnCountChanged ( ) ;
}
2018-10-26 14:24:07 -04:00
2019-04-17 14:25:05 -04:00
/// <summary>Determines whether this instance contains the object.</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
/// <returns>
/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
/// </returns>
2018-10-26 14:24:07 -04:00
public bool Contains ( TElem item ) = > EnumElements ( ) . Contains ( item ) ;
2019-04-17 14:25:05 -04:00
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting
/// at a particular <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">
/// The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from
/// <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.
/// </param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
2019-05-08 11:30:06 -04:00
public void CopyTo ( TElem [ ] array , int arrayIndex ) = > Elements . CopyTo ( array , arrayIndex ) ;
2018-10-26 14:24:07 -04:00
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>A <see cref="IEnumerator{TElem}"/> that can be used to iterate through the collection.</returns>
public IEnumerator < TElem > GetEnumerator ( ) = > EnumElements ( ) . GetEnumerator ( ) ;
2019-04-17 14:25:05 -04:00
/// <summary>Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>.</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
/// <returns>The index of <paramref name="item"/> if found in the list; otherwise, -1.</returns>
2019-05-08 11:30:06 -04:00
public int IndexOf ( TElem item ) = > Array . IndexOf ( Elements , item ) ;
2018-10-26 14:24:07 -04:00
2019-04-17 14:25:05 -04:00
/// <summary>Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index.</summary>
/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
/// <exception cref="ArgumentOutOfRangeException">index</exception>
2019-05-08 11:30:06 -04:00
public virtual void Insert ( int index , TElem item )
2018-10-26 14:24:07 -04:00
{
if ( index < 0 | | index > Count ) throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
2019-07-03 16:45:15 -04:00
if ( IsReadOnly ) throw new InvalidOperationException ( "Array is read-only." ) ;
2019-06-09 12:00:16 -04:00
var newSz = GetRequiredSize ( + + Count , HeaderSize ) ;
2018-10-26 14:24:07 -04:00
var newPtr = mm . AllocMem ( newSz ) ;
2019-05-08 11:30:06 -04:00
var insertPt = ElemOffset ( index ) ;
2018-10-26 14:24:07 -04:00
if ( index > 0 )
CopyTo ( newPtr , 0 , insertPt ) ;
2019-11-19 14:52:44 -05:00
newPtr . Write ( item , insertPt , newSz ) ;
2019-05-09 17:56:31 -04:00
if ( index < Count - 1 )
2019-06-09 12:00:16 -04:00
CopyTo ( newPtr . Offset ( insertPt + ElemSize ) , insertPt , Size - insertPt ) ;
2018-10-26 14:24:07 -04:00
mm . FreeMem ( handle ) ;
SetHandle ( newPtr ) ;
sz = newSz ;
2019-05-08 11:30:06 -04:00
OnCountChanged ( ) ;
2018-10-26 14:24:07 -04:00
}
2019-04-17 14:25:05 -04:00
/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
/// <returns>
/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>;
/// otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
/// </returns>
2018-10-26 14:24:07 -04:00
public bool Remove ( TElem item )
{
var idx = IndexOf ( item ) ;
if ( idx = = - 1 ) return false ;
RemoveAt ( idx ) ;
return true ;
}
2019-04-17 14:25:05 -04:00
/// <summary>Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index.</summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="ArgumentOutOfRangeException">index</exception>
2019-05-08 11:30:06 -04:00
public virtual void RemoveAt ( int index )
2018-10-26 14:24:07 -04:00
{
2019-07-03 16:45:15 -04:00
if ( IsReadOnly ) throw new InvalidOperationException ( "Array is read-only." ) ;
2019-05-08 11:30:06 -04:00
var rmvPt = ElemOffset ( index ) ;
Count - - ;
var newSz = GetRequiredSize ( Count , HeaderSize ) ;
CopyTo ( handle . Offset ( rmvPt ) , rmvPt + ElemSize , newSz - rmvPt ) ;
Size - = ElemSize ;
OnCountChanged ( ) ;
2018-10-26 14:24:07 -04:00
}
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="IEnumerator"/> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable . GetEnumerator ( ) = > GetEnumerator ( ) ;
2019-04-17 14:25:05 -04:00
/// <summary>Copies the values of this array to another native memory pointer.</summary>
/// <param name="ptr">The PTR.</param>
/// <param name="start">The start.</param>
/// <param name="length">The length.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
2018-10-26 14:24:07 -04:00
protected void CopyTo ( IntPtr ptr , int start = 0 , int length = - 1 )
{
2019-05-08 11:30:06 -04:00
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 ) ;
2018-10-26 14:24:07 -04:00
}
2019-04-17 14:25:05 -04:00
/// <summary>Enumerates the elements.</summary>
/// <returns>An enumeration of values from the pointer.</returns>
2019-05-08 11:30:06 -04:00
protected IEnumerable < TElem > EnumElements ( ) = > handle . ToIEnum < TElem > ( 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 ) ) ;
2018-10-26 14:24:07 -04:00
2019-05-08 11:30:06 -04:00
private IntPtr PtrOfElem ( int index ) = > handle . Offset ( ElemOffset ( index ) ) ;
2018-10-26 14:24:07 -04:00
}
}