using System; using System.Runtime.InteropServices; using System.Collections; using System.Collections.Generic; using System.Diagnostics; namespace FreeImageAPI { /// /// Represents unmanaged memory, containing an array of a given structure. /// /// Structuretype represented by the instance. /// /// and can not be marshalled. /// /// Use instead of and /// instead of . /// public unsafe class MemoryArray : IDisposable, ICloneable, ICollection, IEnumerable, IEquatable> where T : struct { /// /// Baseaddress of the wrapped memory. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected byte* baseAddress; /// /// Number of elements being wrapped. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected int length; /// /// Size, in bytes, of each element. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] private static readonly int size; /// /// Array of T containing a single element. /// The array is used as a workaround, because there are no pointer for generic types. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected T[] buffer; /// /// Pointer to the element of buffer. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected byte* ptr; /// /// Handle for pinning buffer. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected GCHandle handle; /// /// Indicates whether the wrapped memory is handled like a bitfield. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected readonly bool isOneBit; /// /// Indicates whther the wrapped memory is handles like 4-bit blocks. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected readonly bool isFourBit; /// /// An object that can be used to synchronize access to the . /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] protected object syncRoot = null; static MemoryArray() { T[] dummy = new T[2]; long marshalledSize = Marshal.SizeOf(typeof(T)); long structureSize = Marshal.UnsafeAddrOfPinnedArrayElement(dummy, 1).ToInt64() - Marshal.UnsafeAddrOfPinnedArrayElement(dummy, 0).ToInt64(); if (marshalledSize != structureSize) { throw new NotSupportedException( "The desired type can not be handled, " + "because its managed and unmanaged size in bytes are different."); } size = (int)marshalledSize; } /// /// Initializes a new instance. /// protected MemoryArray() { } /// /// Initializes a new instance of the class. /// /// Address of the memory block. /// Length of the array. /// /// is null. /// /// is less or equal zero. /// /// The type is not supported. public MemoryArray(IntPtr baseAddress, int length) : this(baseAddress.ToPointer(), length) { } /// /// Initializes a new instance of the class. /// /// Address of the memory block. /// Length of the array. /// /// is null. /// /// is less or equal zero. /// /// The type is not supported. public MemoryArray(void* baseAddress, int length) { if (typeof(T) == typeof(FI1BIT)) { isOneBit = true; } else if (typeof(T) == typeof(FI4BIT)) { isFourBit = true; } if (baseAddress == null) { throw new ArgumentNullException("baseAddress"); } if (length < 1) { throw new ArgumentOutOfRangeException("length"); } this.baseAddress = (byte*)baseAddress; this.length = (int)length; if (!isOneBit && !isFourBit) { // Create an array containing a single element. // Due to the fact, that it's not possible to create pointers // of generic types, an array is used to obtain the memory // address of an element of T. this.buffer = new T[1]; // The array is pinned immediately to prevent the GC from // moving it to a different position in memory. this.handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); // The array and its content have beed pinned, so that its address // can be safely requested and stored for the whole lifetime // of the instace. this.ptr = (byte*)handle.AddrOfPinnedObject(); } } /// /// Frees the allocated . /// ~MemoryArray() { Dispose(false); } /// /// Tests whether two specified structures are equivalent. /// /// The that is to the left of the equality operator. /// The that is to the right of the equality operator. /// /// true if the two structures are equal; otherwise, false. /// public static bool operator ==(MemoryArray left, MemoryArray right) { if (object.ReferenceEquals(left, right)) { return true; } if (object.ReferenceEquals(right, null) || object.ReferenceEquals(left, null) || (left.length != right.length)) { return false; } if (left.baseAddress == right.baseAddress) { return true; } return FreeImage.CompareMemory(left.baseAddress, right.baseAddress, (uint)left.length); } /// /// Tests whether two specified structures are different. /// /// The that is to the left of the inequality operator. /// The that is to the right of the inequality operator. /// /// true if the two structures are different; otherwise, false. /// public static bool operator !=(MemoryArray left, MemoryArray right) { return (!(left == right)); } /// /// Gets the value at the specified position. /// /// A 32-bit integer that represents the position /// of the array element to get. /// The value at the specified position. /// /// is outside the range of valid indexes /// for the unmanaged array. public T GetValue(int index) { if ((index >= this.length) || (index < 0)) { throw new ArgumentOutOfRangeException("index"); } return GetValueInternal(index); } private T GetValueInternal(int index) { EnsureNotDisposed(); if (isOneBit) { return (T)(object)(FI1BIT)(((baseAddress[index / 8] & ((1 << (7 - (index % 8))))) == 0) ? 0 : 1); } else if (isFourBit) { return (T)(object)(FI4BIT)(((index % 2) == 0) ? (baseAddress[index / 2] >> 4) : (baseAddress[index / 2] & 0x0F)); } else { CopyMemory(ptr, baseAddress + (index * size), size); return buffer[0]; } } /// /// Sets a value to the element at the specified position. /// /// The new value for the specified element. /// A 32-bit integer that represents the /// position of the array element to set. /// /// is outside the range of valid indexes /// for the unmanaged array. public void SetValue(T value, int index) { if ((index >= this.length) || (index < 0)) { throw new ArgumentOutOfRangeException("index"); } SetValueInternal(value, index); } private void SetValueInternal(T value, int index) { EnsureNotDisposed(); if (isOneBit) { if ((FI1BIT)(object)value != 0) { baseAddress[index / 8] |= (byte)(1 << (7 - (index % 8))); } else { baseAddress[index / 8] &= (byte)(~(1 << (7 - (index % 8)))); } } else if (isFourBit) { if ((index % 2) == 0) { baseAddress[index / 2] = (byte)((baseAddress[index / 2] & 0x0F) | ((FI4BIT)(object)value << 4)); } else { baseAddress[index / 2] = (byte)((baseAddress[index / 2] & 0xF0) | ((FI4BIT)(object)value & 0x0F)); } } else { buffer[0] = value; CopyMemory(baseAddress + (index * size), ptr, size); } } /// /// Gets the values at the specified position and length. /// /// A 32-bit integer that represents the position /// of the array elements to get. /// A 32-bit integer that represents the length /// of the array elements to get. /// The values at the specified position and length. /// /// is outside the range of valid indexes /// for the unmanaged array or is greater than the number of elements /// from to the end of the unmanaged array. public T[] GetValues(int index, int length) { EnsureNotDisposed(); if ((index >= this.length) || (index < 0)) { throw new ArgumentOutOfRangeException("index"); } if (((index + length) > this.length) || (length < 1)) { throw new ArgumentOutOfRangeException("length"); } T[] data = new T[length]; if (isOneBit || isFourBit) { for (int i = 0; i < length; i++) { data[i] = GetValueInternal(i); } } else { GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); byte* dst = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(data, 0); CopyMemory(dst, baseAddress + (size * index), size * length); handle.Free(); } return data; } /// /// Sets the values at the specified position. /// /// An array containing the new values for the specified elements. /// A 32-bit integer that represents the position /// of the array elements to set. /// /// is a null reference (Nothing in Visual Basic). /// /// is outside the range of valid indexes /// for the unmanaged array or is greater than the number of elements /// from to the end of the array. public void SetValues(T[] values, int index) { EnsureNotDisposed(); if (values == null) { throw new ArgumentNullException("values"); } if ((index >= this.length) || (index < 0)) { throw new ArgumentOutOfRangeException("index"); } if ((index + values.Length) > this.length) { throw new ArgumentOutOfRangeException("values.Length"); } if (isOneBit || isFourBit) { for (int i = 0; i != values.Length; ) { SetValueInternal(values[i++], index++); } } else { GCHandle handle = GCHandle.Alloc(values, GCHandleType.Pinned); byte* src = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(values, 0); CopyMemory(baseAddress + (index * size), src, size * length); handle.Free(); } } /// /// Copies the entire array to a compatible one-dimensional , /// starting at the specified index of the target array. /// /// 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(Array array, int index) { EnsureNotDisposed(); if (!(array is T[])) { throw new InvalidCastException("array"); } try { CopyTo((T[])array, 0, index, length); } catch (ArgumentOutOfRangeException ex) { throw new ArgumentException(ex.Message, ex); } } /// /// Copies a range of elements from the unmanaged array starting at the specified /// and pastes them to /// starting at the specified . /// The length and the indexes are specified as 32-bit integers. /// /// The array that receives the data. /// A 32-bit integer that represents the index /// in the unmanaged array at which copying begins. /// A 32-bit integer that represents the index in /// the destination array at which storing begins. /// A 32-bit integer that represents the number of elements to copy. /// /// is a null reference (Nothing in Visual Basic). /// /// is outside the range of valid indexes /// for the unmanaged array or is greater than the number of elements /// from to the end of the unmanaged array /// -or- /// is outside the range of valid indexes /// for the array or is greater than the number of elements /// from to the end of the array. /// public void CopyTo(T[] array, int sourceIndex, int destinationIndex, int length) { EnsureNotDisposed(); if (array == null) { throw new ArgumentNullException("array"); } if ((sourceIndex >= this.length) || (sourceIndex < 0)) { throw new ArgumentOutOfRangeException("sourceIndex"); } if ((destinationIndex >= array.Length) || (destinationIndex < 0)) { throw new ArgumentOutOfRangeException("destinationIndex"); } if ((sourceIndex + length > this.length) || (destinationIndex + length > array.Length) || (length < 1)) { throw new ArgumentOutOfRangeException("length"); } if (isOneBit || isFourBit) { for (int i = 0; i != length; i++) { array[destinationIndex++] = GetValueInternal(sourceIndex++); } } else { GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned); byte* dst = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(array, destinationIndex); CopyMemory(dst, baseAddress + (size * sourceIndex), size * length); handle.Free(); } } /// /// Copies a range of elements from the array starting at the specified /// and pastes them to the unmanaged array /// starting at the specified . /// The length and the indexes are specified as 32-bit integers. /// /// The array that holds the data. /// A 32-bit integer that represents the index /// in the array at which copying begins. /// A 32-bit integer that represents the index in /// the unmanaged array at which storing begins. /// A 32-bit integer that represents the number of elements to copy. /// /// is a null reference (Nothing in Visual Basic). /// /// is outside the range of valid indexes /// for the array or is greater than the number of elements /// from to the end of the array /// -or- /// is outside the range of valid indexes /// for the unmanaged array or is greater than the number of elements /// from to the end of the unmanaged array. /// public void CopyFrom(T[] array, int sourceIndex, int destinationIndex, int length) { EnsureNotDisposed(); if (array == null) { throw new ArgumentNullException("array"); } if ((destinationIndex >= this.length) || (destinationIndex < 0)) { throw new ArgumentOutOfRangeException("destinationIndex"); } if ((sourceIndex >= array.Length) || (sourceIndex < 0)) { throw new ArgumentOutOfRangeException("sourceIndex"); } if ((destinationIndex + length > this.length) || (sourceIndex + length > array.Length) || (length < 1)) { throw new ArgumentOutOfRangeException("length"); } if (isOneBit || isFourBit) { for (int i = 0; i != length; i++) { SetValueInternal(array[sourceIndex++], destinationIndex++); } } else { GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned); byte* src = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(array, sourceIndex); CopyMemory(baseAddress + (size * destinationIndex), src, size * length); handle.Free(); } } /// /// Returns the represented block of memory as an array of . /// /// The represented block of memory. public byte[] ToByteArray() { EnsureNotDisposed(); byte[] result; if (isOneBit) { result = new byte[(length + 7) / 8]; } else if (isFourBit) { result = new byte[(length + 3) / 4]; } else { result = new byte[size * length]; } fixed (byte* dst = result) { CopyMemory(dst, baseAddress, result.Length); } return result; } /// /// Gets or sets the value at the specified position in the array. /// /// A 32-bit integer that represents the position /// of the array element to get. /// The value at the specified position in the array. /// /// is outside the range of valid indexes /// for the unmanaged array. public T this[int index] { get { return GetValue(index); } set { SetValue(value, index); } } /// /// Gets or sets the values of the unmanaged array. /// public T[] Data { get { return GetValues(0, length); } set { if (value == null) { throw new ArgumentNullException("value"); } if (value.Length != length) { throw new ArgumentOutOfRangeException("value.Lengt"); } SetValues(value, 0); } } /// /// Gets the length of the unmanaged array. /// public int Length { get { EnsureNotDisposed(); return length; } } /// /// Gets the base address of the represented memory block. /// public IntPtr BaseAddress { get { EnsureNotDisposed(); return new IntPtr(baseAddress); } } /// /// Creates a shallow copy of the . /// /// A shallow copy of the . public object Clone() { EnsureNotDisposed(); return new MemoryArray(baseAddress, length); } /// /// Gets a 32-bit integer that represents the total number of elements /// in the . /// public int Count { get { EnsureNotDisposed(); return length; } } /// /// Gets a value indicating whether access to the /// is synchronized (thread safe). /// public bool IsSynchronized { get { EnsureNotDisposed(); return false; } } /// /// Gets an object that can be used to synchronize access to the . /// public object SyncRoot { get { EnsureNotDisposed(); if (syncRoot == null) { System.Threading.Interlocked.CompareExchange(ref syncRoot, new object(), null); } return syncRoot; } } /// /// Retrieves an object that can iterate through the individual /// elements in this . /// /// An for the . public IEnumerator GetEnumerator() { EnsureNotDisposed(); T[] values = GetValues(0, length); for (int i = 0; i != values.Length; i++) { yield return values[i]; } } /// /// Retrieves an object that can iterate through the individual /// elements in this . /// /// An for the . IEnumerator IEnumerable.GetEnumerator() { EnsureNotDisposed(); T[] values = GetValues(0, length); for (int i = 0; i != values.Length; i++) { yield return values[i]; } } /// /// Releases all ressources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases allocated handles associated with this instance. /// /// true to release managed resources. protected virtual void Dispose(bool disposing) { if (baseAddress != null) { if (handle.IsAllocated) handle.Free(); baseAddress = null; buffer = null; length = 0; syncRoot = null; } } /// /// Throws an if /// this instance is disposed. /// protected virtual void EnsureNotDisposed() { if (baseAddress == null) throw new ObjectDisposedException("This instance is disposed."); } /// /// Tests whether the specified structure is equivalent to this /// structure. /// /// The structure to test. /// true if is a /// instance equivalent to this structure; otherwise, /// false. public override bool Equals(object obj) { EnsureNotDisposed(); return ((obj is MemoryArray) && Equals((MemoryArray)obj)); } /// /// Tests whether the specified structure is equivalent to this /// structure. /// /// The structure to test. /// true if is equivalent to this /// structure; otherwise, /// false. public bool Equals(MemoryArray other) { EnsureNotDisposed(); return ((this.baseAddress == other.baseAddress) && (this.length == other.length)); } /// /// Serves as a hash function for a particular type. /// /// A hash code for the current . public override int GetHashCode() { EnsureNotDisposed(); return (int)baseAddress ^ length; } /// /// Copies a block of memory from one location to another. /// /// Pointer to the starting address of the copy destination. /// Pointer to the starting address of the block of memory to be copied. /// Size of the block of memory to copy, in bytes. protected static unsafe void CopyMemory(byte* dest, byte* src, int len) { if (len >= 0x10) { do { *((int*)dest) = *((int*)src); *((int*)(dest + 4)) = *((int*)(src + 4)); *((int*)(dest + 8)) = *((int*)(src + 8)); *((int*)(dest + 12)) = *((int*)(src + 12)); dest += 0x10; src += 0x10; } while ((len -= 0x10) >= 0x10); } if (len > 0) { if ((len & 8) != 0) { *((int*)dest) = *((int*)src); *((int*)(dest + 4)) = *((int*)(src + 4)); dest += 8; src += 8; } if ((len & 4) != 0) { *((int*)dest) = *((int*)src); dest += 4; src += 4; } if ((len & 2) != 0) { *((short*)dest) = *((short*)src); dest += 2; src += 2; } if ((len & 1) != 0) { *dest = *src; } } } } }