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