using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; namespace Vanara.InteropServices { /// An safe unmanaged array of bytes allocated on the global heap. /// /// /// /// public class SafeByteArray : SafeMemoryHandle, IList, ICloneable, IList, IStructuralComparable, IStructuralEquatable { /// Initializes a new instance of the class from a copy of a managed byte array. /// The array of bytes to copy. public SafeByteArray(byte[] array) : base(array?.Length ?? 0) { if (array != null) Marshal.Copy(array, 0, handle, array.Length); } /// Initializes a new instance of the class and allocates bytes. /// The byte count to allocate. public SafeByteArray(int byteCount) : base(byteCount) => Zero(); /// /// Initializes a new instance of the class by copying the bytes from another unmanaged array. /// /// Another unmanaged array. public SafeByteArray(SafeByteArray src) : base(src?.Count ?? 0) { if (src == null) throw new ArgumentNullException(nameof(src)); if (src.IsInvalid) throw new ArgumentException(@"Invalid source object.", nameof(src)); CopyMemory(src.handle, handle, src.Count); } // Prevents construction of an invalid instance. [ExcludeFromCodeCoverage] private SafeByteArray() : base(0) { } /// Gets the number of elements contained in the . public int Count => Size; /// Gets a value indicating whether the is read-only. public bool IsReadOnly => false; /// Gets a value indicating whether the has a fixed size. bool IList.IsFixedSize => true; /// /// Gets a value indicating whether access to the is synchronized (thread safe). /// bool ICollection.IsSynchronized => true; /// Gets an object that can be used to synchronize access to the . object ICollection.SyncRoot => this; [ExcludeFromCodeCoverage] private new int Size { get => base.Size; set => base.Size = value; } /// Gets or sets the at the specified index. /// The . /// The index. /// /// index /// Object is not valid. public byte this[int index] { get { if (index < 0 || index >= Count) throw new IndexOutOfRangeException(); return Marshal.ReadByte(handle, index); } set { if (index < 0 || index >= Count) throw new IndexOutOfRangeException(); Marshal.WriteByte(handle, index, value); } } /// Gets or sets the at the specified index. /// The . /// The index. /// object IList.this[int index] { get => this[index]; set => this[index] = (byte)value; } /// Removes all items from the . /// public void Clear() => Size = 0; /// Creates a new object that is a copy of the current instance. /// A new object that is a copy of this instance. public object Clone() => new SafeByteArray(this); /// Determines whether the contains a specific value. /// The object to locate in the . /// /// true if is found in the ; otherwise, false. /// public bool Contains(byte item) => IndexOf(item) != -1; /// /// 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. /// array /// array public void CopyTo(byte[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array)); if (array.Length - arrayIndex < Count) throw new ArgumentOutOfRangeException(nameof(array)); Marshal.Copy(handle, array, arrayIndex, Count); } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() { if (IsInvalid) yield break; for (var i = 0; i < Count; i++) yield return Marshal.ReadByte(handle, i); } /// 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(byte item) { var i = 0; foreach (var b in this) { if (b == item) return i; i++; } return -1; } /// Copies unmanaged bytes to a managed byte array. /// Copied byte array. public byte[] ToArray() { var array = new byte[Count]; CopyTo(array, 0); return array; } /// Adds an item to the . /// The object to add to the . /// /// The position into which the new element was inserted, or -1 to indicate that the item was not inserted into the collection. /// int IList.Add(object value) => throw new NotSupportedException(); /// Adds an item to the . /// The object to add to the . /// [ExcludeFromCodeCoverage] void ICollection.Add(byte item) => ((IList)this).Add(item); /// /// Determines whether the current collection object precedes, occurs in the same position as, or follows another object in the sort order. /// /// The object to compare with the current instance. /// /// An object that compares members of the current collection object with the corresponding members of . /// /// /// An integer that indicates the relationship of the current collection object to , as shown in the /// following table.Return valueDescription-1The current instance precedes .0The current instance and /// are equal.1The current instance follows . /// int IStructuralComparable.CompareTo(object other, IComparer comparer) { if (comparer == null) throw new ArgumentNullException(nameof(comparer)); if (other == null) return 1; if (!(other is IEnumerable o)) throw new ArgumentOutOfRangeException(nameof(other), @"Other value is not enumerable."); var l = other as ICollection ?? new List(o); if (Count != l.Count) throw new ArgumentOutOfRangeException(nameof(other), @"Other value doesn't have the same number of elements."); using (var tenum = GetEnumerator()) using (var oenum = l.GetEnumerator()) { while (tenum.MoveNext() && oenum.MoveNext()) { var i = comparer.Compare(tenum.Current, oenum.Current); if (i != 0) return i; } } return 0; } /// Determines whether the contains a specific value. /// The object to locate in the . /// true if the is found in the ; otherwise, false. bool IList.Contains(object value) => value != null && Contains((byte)value); /// /// 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. /// array /// array void ICollection.CopyTo(Array array, int index) { if (array == null) throw new ArgumentNullException(nameof(array)); if (array.Rank != 1 || array.Length - index < Count) throw new ArgumentOutOfRangeException(nameof(array)); for (var i = 0; i < Count; i++) array.SetValue(this[i], i + index); } /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// The comparer. /// true if the specified is equal to this instance; otherwise, false. bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { if (comparer == null) throw new ArgumentNullException(nameof(comparer)); if (other == null) return false; if (ReferenceEquals(this, other)) return true; if (!(other is IEnumerable o)) throw new ArgumentOutOfRangeException(nameof(other), @"Other value is not enumerable."); var l = other as ICollection ?? new List(o); if (Count != l.Count) throw new ArgumentOutOfRangeException(nameof(other), @"Other value doesn't have the same number of elements."); using (var tenum = GetEnumerator()) using (var oenum = l.GetEnumerator()) { while (tenum.MoveNext() && oenum.MoveNext()) { if (!comparer.Equals(tenum.Current, oenum.Current)) return false; } } return true; } /// Returns an enumerator that iterates through a collection. /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// Returns a hash code for this instance. /// The comparer. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// comparer int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) { if (comparer == null) throw new ArgumentNullException(nameof(comparer)); var ret = 0; for (var i = Size >= 8 ? Size - 8 : 0; i < Size; i++) ret = CombineHashCodes(ret, comparer.GetHashCode(this[i])); return ret; } /// 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. int IList.IndexOf(object value) => IndexOf((byte)value); /// Inserts an item to the at the specified index. /// The zero-based index at which should be inserted. /// The object to insert into the . [ExcludeFromCodeCoverage] void IList.Insert(int index, object value) => ((IList)this).Insert(index, (byte)value); /// Inserts an item to the at the specified index. /// The zero-based index at which should be inserted. /// The object to insert into the . /// void IList.Insert(int index, byte item) => throw new NotSupportedException(); /// Removes the first occurrence of a specific object from the . /// The object to remove from the . [ExcludeFromCodeCoverage] void IList.Remove(object value) => ((ICollection)this).Remove((byte)value); /// 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 . /// /// bool ICollection.Remove(byte item) => throw new NotSupportedException(); /// Removes the item at the specified index. /// The zero-based index of the item to remove. /// void IList.RemoveAt(int index) => throw new NotSupportedException(); /// Removes the item at the specified index. /// The zero-based index of the item to remove. [ExcludeFromCodeCoverage] void IList.RemoveAt(int index) => ((IList)this).RemoveAt(index); private static int CombineHashCodes(int h1, int h2) => ((h1 << 5) + h1) ^ h2; private static void CopyMemory(IntPtr src, IntPtr dest, int length) { var size1 = length % 8; var size8 = length - size1; int ofs; // copy multiples of 8 bytes first for (ofs = 0; ofs < size8; ofs += 8) Marshal.WriteInt64(dest, ofs, Marshal.ReadInt64(src, ofs)); // copy remaining bytes for (var n = 0; n < size1; n++, ofs++) Marshal.WriteByte(dest, ofs, Marshal.ReadByte(src, ofs)); } } }