using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using Vanara.Extensions; namespace Vanara.InteropServices { /// Method used to pack a list of strings into memory. public enum StringListPackMethod { /// Each string is separated by a single '\0' character and is terminated by two '\0' characters. Concatenated, /// A contiguous block of memory containing an array of pointers to strings followed by a NULL pointer and then followed by the actual strings. Packed } /// Interface to capture unmanaged memory methods. public interface IMemoryMethods { /// Gets the allocation method. Func AllocMem { get; } /// Gets the Ansi allocation method. Func AllocSecureStringAnsi { get; } /// Gets the Unicode allocation method. Func AllocSecureStringUni { get; } /// Gets the Ansi string allocation method. Func AllocStringAnsi { get; } /// Gets the Unicode string allocation method. Func AllocStringUni { get; } /// Gets the free method. Action FreeMem { get; } /// Gets the Ansi free method. Action FreeSecureStringAnsi { get; } /// Gets the Unicode free method. Action FreeSecureStringUni { get; } /// Gets the reallocation method. Func ReAllocMem { get; } } /// Interface for classes that support safe memory pointers. public interface ISafeMemoryHandle { /// Gets a value indicating whether the handle value is invalid. bool IsInvalid { get; } /// Gets the size of the allocated memory block. /// The size of the allocated memory block. int Size { get; set; } /// /// Adds reference to other SafeMemoryHandle objects, the pointer to which are referred to by this object. This is to ensure that such objects being /// referred to wouldn't be unreferenced until this object is active. For e.g. when this object is an array of pointers to other objects /// /// Collection of SafeMemoryHandle objects referred to by this object. void AddSubReference(IEnumerable children); /// Returns the instance as an . This is a dangerous call as the value is mutable. /// An to the internally held memory. IntPtr DangerousGetHandle(); /// /// Extracts an array of structures of containing items. This call can cause memory /// exceptions if the pointer does not have sufficient allocated memory to retrieve all the structures. /// /// The type of the structures to retrieve. /// The number of structures to retrieve. /// The number of bytes to skip before reading the structures. /// An array of structures of . T[] ToArray(int count, int prefixBytes = 0); /// /// Extracts an enumeration of structures of containing items. This call can cause /// memory exceptions if the pointer does not have sufficient allocated memory to retrieve all the structures. /// /// The type of the structures to retrieve. /// The number of structures to retrieve. /// The number of bytes to skip before reading the structures. /// An enumeration of structures of . IEnumerable ToEnumerable(int count, int prefixBytes = 0); /// Returns a that represents this instance. /// The length. /// The character set of the string. /// A that represents this instance. string ToString(int len, CharSet charSet = CharSet.Unicode); /// /// Gets an enumerated list of strings from a block of unmanaged memory where each string is separated by a single '\0' character and is terminated by /// two '\0' characters. /// /// The character set of the strings. /// Number of bytes preceding the array of string pointers. /// Enumeration of strings. IEnumerable ToStringEnum(CharSet charSet = CharSet.Auto, int prefixBytes = 0); /// Returns an enumeration of strings from memory where each string is pointed to by a preceding list of pointers of length . /// The count. /// The character set of the strings. /// Number of bytes preceding the array of string pointers. /// An enumerated list of strings. IEnumerable ToStringEnum(int count, CharSet charSet = CharSet.Auto, int prefixBytes = 0); /// Marshals data from this block of memory to a newly allocated managed object of the type specified by a generic type parameter. /// The type of the object to which the data is to be copied. This must be a structure. /// A managed object that contains the data that this holds. T ToStructure(); } /// Abstract base class for all SafeHandle derivatives that encapsulate handling unmanaged memory. /// public abstract class SafeAllocatedMemoryHandle : SafeHandle { /// Initializes a new instance of the class. /// The handle. /// if set to true if this class is responsible for freeing the memory on disposal. protected SafeAllocatedMemoryHandle(IntPtr handle, bool ownsHandle) : base(IntPtr.Zero, ownsHandle) => SetHandle(handle); /// Gets or sets the size in bytes of the allocated memory block. /// The size in bytes of the allocated memory block. public abstract int Size { get; set; } /// Zero out all allocated memory. public virtual void Zero() => Fill(0, Size); /// Fills the allocated memory with a specific byte value. /// The byte value. public virtual void Fill(byte value) => Fill(value, Size); /// Fills the allocated memory with a specific byte value. /// The byte value. /// The number of bytes in the block of memory to be filled. public virtual void Fill(byte value, int length) { if (length > Size) throw new ArgumentOutOfRangeException(nameof(length)); Vanara.Extensions.InteropExtensions.FillMemory(handle, value, length); } } /// Abstract base class for all SafeAllocatedMemoryHandle derivatives that apply a specific memory handling routine set. /// The implementation. public abstract class SafeMemoryHandle : SafeAllocatedMemoryHandle where TMem : IMemoryMethods, new() { /// The implementation instance. protected TMem mm = new TMem(); /// The number of bytes currently allocated. protected int sz; /// Initializes a new instance of the class. /// The size of memory to allocate, in bytes. /// size - The value of this argument must be non-negative protected SafeMemoryHandle(int size = 0) : base(IntPtr.Zero, true) { if (size < 0) throw new ArgumentOutOfRangeException(nameof(size), "The value of this argument must be non-negative"); if (size == 0) return; RuntimeHelpers.PrepareConstrainedRegions(); SetHandle(mm.AllocMem(sz = size)); } /// 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. protected SafeMemoryHandle(IntPtr handle, int size, bool ownsHandle) : base(handle, ownsHandle) => sz = size; /// Allocates from unmanaged memory to represent an array of pointers and marshals the unmanaged pointers (IntPtr) to the native array equivalent. /// Array of unmanaged pointers /// SafeHGlobalHandle object to an native (unmanaged) array of pointers protected SafeMemoryHandle(byte[] bytes) : this(bytes.Length) => Marshal.Copy(bytes, 0, handle, bytes.Length); /// When overridden in a derived class, gets a value indicating whether the handle value is invalid. public override bool IsInvalid => handle == IntPtr.Zero; /// Gets or sets the size in bytes of the allocated memory block. /// The size in bytes of the allocated memory block. public override int Size { get => sz; set { if (value == 0) { ReleaseHandle(); } else { handle = IsInvalid ? mm.AllocMem(value) : mm.ReAllocMem(handle, value); if (sz != 0) Marshal.Copy(new byte[value], 0, handle, value); sz = value; } } } /// Returns the as an . This is a dangerous call as the value is mutable. /// The instance. /// The result of the conversion. public static explicit operator IntPtr(SafeMemoryHandle h) => h.DangerousGetHandle(); /// Gets a copy of bytes from the allocated memory block. /// The start index. /// The number of bytes to retrieve. /// A byte array with the copied bytes. protected byte[] GetBytes(int startIndex, int count) { if (startIndex < 0 || startIndex + count > Size) throw new ArgumentOutOfRangeException(); var ret = new byte[count]; Marshal.Copy(handle, ret, startIndex, count); return ret; } /// When overridden in a derived class, executes the code required to free the handle. /// /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a /// releaseHandleFailed MDA Managed Debugging Assistant. /// protected override bool ReleaseHandle() { mm.FreeMem(handle); sz = 0; handle = IntPtr.Zero; return true; } } /// A for memory allocated via COM. /// public abstract class SafeMemoryHandleExt : SafeMemoryHandle, ISafeMemoryHandle where TMem : IMemoryMethods, new() { /// /// Maintains reference to other SafeMemoryHandleExt objects, the pointer to which are referred to by this object. This is to ensure that such objects /// being referred to wouldn't be unreferenced until this object is active. /// private List references; /// Initializes a new instance of the class. /// The size of memory to allocate, in bytes. /// size - The value of this argument must be non-negative protected SafeMemoryHandleExt(int size) : base(size) { } /// 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. protected SafeMemoryHandleExt(IntPtr handle, int size, bool ownsHandle) : base(handle, size, ownsHandle) { } /// Allocates from unmanaged memory to represent an array of pointers and marshals the unmanaged pointers (IntPtr) to the native array equivalent. /// Array of unmanaged pointers /// SafeHGlobalHandle object to an native (unmanaged) array of pointers protected SafeMemoryHandleExt(byte[] bytes) : base(bytes) { } /// Allocates from unmanaged memory to represent an array of pointers and marshals the unmanaged pointers (IntPtr) to the native array equivalent. /// Array of unmanaged pointers /// SafeMemoryHandleExt object to an native (unmanaged) array of pointers protected SafeMemoryHandleExt(IntPtr[] values) : this(IntPtr.Size * values.Length) => Marshal.Copy(values, 0, handle, values.Length); /// Allocates from unmanaged memory to represent a Unicode string (WSTR) and marshal this to a native PWSTR. /// The string value. /// The character set of the string. /// SafeMemoryHandleExt object to an native (unmanaged) string protected SafeMemoryHandleExt(string s, CharSet charSet = CharSet.Unicode) : base(IntPtr.Zero, StringHelper.GetByteCount(s, true, charSet), true) { RuntimeHelpers.PrepareConstrainedRegions(); SetHandle(StringHelper.GetCharSize(charSet) == 2 ? mm.AllocStringUni(s) : mm.AllocStringAnsi(s)); } /// /// Adds reference to other SafeMemoryHandle objects, the pointer to which are referred to by this object. This is to ensure that such objects being /// referred to wouldn't be unreferenced until this object is active. For e.g. when this object is an array of pointers to other objects /// /// Collection of SafeMemoryHandle objects referred to by this object. public void AddSubReference(IEnumerable children) { if (references == null) references = new List(); references.AddRange(children); } /// Extracts an array of structures of containing items. /// This call can cause memory exceptions if the pointer does not have sufficient allocated memory to retrieve all the structures. /// /// The type of the structures to retrieve. /// The number of structures to retrieve. /// The number of bytes to skip before reading the structures. /// An array of structures of . public T[] ToArray(int count, int prefixBytes = 0) { if (IsInvalid) return null; //if (Size < Marshal.SizeOf(typeof(T)) * count + prefixBytes) // throw new InsufficientMemoryException("Requested array is larger than the memory allocated."); if (!typeof(T).IsBlittable()) throw new ArgumentException(@"Structure layout is not sequential or explicit."); Debug.Assert(typeof(T).StructLayoutAttribute?.Value == LayoutKind.Sequential); return handle.ToArray(count, prefixBytes); } /// Extracts an enumeration of structures of containing items. /// This call can cause memory exceptions if the pointer does not have sufficient allocated memory to retrieve all the structures. /// /// The type of the structures to retrieve. /// The number of structures to retrieve. /// The number of bytes to skip before reading the structures. /// An enumeration of structures of . public IEnumerable ToEnumerable(int count, int prefixBytes = 0) { if (IsInvalid) return new T[0]; //if (Size < Marshal.SizeOf(typeof(T)) * count + prefixBytes) // throw new InsufficientMemoryException("Requested array is larger than the memory allocated."); if (!typeof(T).IsBlittable()) throw new ArgumentException(@"Structure layout is not sequential or explicit."); Debug.Assert(typeof(T).StructLayoutAttribute?.Value == LayoutKind.Sequential); return handle.ToIEnum(count, prefixBytes); } /// Returns a that represents this instance. /// The length. /// The character set of the string. /// A that represents this instance. public string ToString(int len, CharSet charSet = CharSet.Unicode) => len == -1 ? StringHelper.GetString(handle, charSet) : StringHelper.GetString(handle, charSet).Substring(0, len); /// Returns an enumeration of strings from memory where each string is pointed to by a preceding list of pointers of length . /// The count of expected strings. /// The character set of the strings. /// Number of bytes preceding the array of string pointers. /// Enumeration of strings. public IEnumerable ToStringEnum(int count, CharSet charSet = CharSet.Auto, int prefixBytes = 0) => IsInvalid ? new string[0] : InteropExtensions.ToStringEnum(handle, count, charSet, prefixBytes); /// /// Gets an enumerated list of strings from a block of unmanaged memory where each string is separated by a single '\0' character and is terminated by /// two '\0' characters. /// /// The character set of the strings. /// Number of bytes preceding the array of string pointers. /// An enumerated list of strings. public IEnumerable ToStringEnum(CharSet charSet = CharSet.Auto, int prefixBytes = 0) => IsInvalid ? new string[0] : InteropExtensions.ToStringEnum(handle, charSet, prefixBytes); /// Marshals data from this block of memory to a newly allocated managed object of the type specified by a generic type parameter. /// The type of the object to which the data is to be copied. This must be a structure. /// A managed object that contains the data that this holds. public T ToStructure() { if (IsInvalid) return default; //if (Size < Marshal.SizeOf(typeof(T))) // throw new InsufficientMemoryException("Requested structure is larger than the memory allocated."); return handle.ToStructure(); } #if DEBUG [ExcludeFromCodeCoverage] public string Dump => Size == 0 ? "" : string.Join(" ", ToEnumerable(Size).Select(b => b.ToString("X2")).ToArray()); #endif /// When overridden in a derived class, executes the code required to free the handle. /// /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a /// releaseHandleFailed MDA Managed Debugging Assistant. /// protected override bool ReleaseHandle() { var released = base.ReleaseHandle(); handle = IntPtr.Zero; return released; } } }