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; using Vanara.PInvoke; 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 : ISimpleMemoryMethods { /// Gets the Ansi allocation method. /// The secure string. /// A memory handle. IntPtr AllocSecureStringAnsi(SecureString secureString); /// Gets the Unicode allocation method. /// The secure string. /// A memory handle. IntPtr AllocSecureStringUni(SecureString secureString); /// Gets the Ansi string allocation method. /// The value. /// A memory handle. IntPtr AllocStringAnsi(string value); /// Gets the Unicode string allocation method. /// The value. /// A memory handle. IntPtr AllocStringUni(string value); /// Gets the Ansi free method. /// A memory handle. void FreeSecureStringAnsi(IntPtr hMem); /// Gets the Unicode free method. /// A memory handle. void FreeSecureStringUni(IntPtr hMem); /// Gets the reallocation method. /// A memory handle. /// The size, in bytes, of memory to allocate. /// A memory handle. IntPtr ReAllocMem(IntPtr hMem, int size); } /// Interface for classes that support safe memory pointers. public interface ISafeMemoryHandle : IDisposable { /// 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. SizeT 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); /// Returns a that represents this instance. /// The length. /// Number of bytes preceding the string pointer. /// The character set of the string. /// A that represents this instance. string ToString(int len, int prefixBytes, 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. /// Number of bytes preceding the structure. /// A managed object that contains the data that this holds. T ToStructure(int prefixBytes = 0); } /// Interface to capture unmanaged simple (alloc/free) memory methods. public interface ISimpleMemoryMethods { /// Gets a handle to a memory allocation of the specified size. /// The size, in bytes, of memory to allocate. /// A memory handle. IntPtr AllocMem(int size); /// Frees the memory associated with a handle. /// A memory handle. void FreeMem(IntPtr hMem); /// Locks the memory of a specified handle and gets a pointer to it. /// A memory handle. /// A pointer to the locked memory. IntPtr LockMem(IntPtr hMem); /// Unlocks the memory of a specified handle. /// A memory handle. void UnlockMem(IntPtr hMem); } /// Implementation of using just the methods from . /// public abstract class MemoryMethodsBase : IMemoryMethods { /// /// Gets a handle to a memory allocation of the specified size. /// /// The size, in bytes, of memory to allocate. /// /// A memory handle. /// /// public abstract IntPtr AllocMem(int size); /// Gets the Ansi allocation method. /// The secure string. /// A memory handle. public virtual IntPtr AllocSecureStringAnsi(SecureString secureString) => StringHelper.AllocSecureString(secureString, CharSet.Ansi, AllocMem); /// Gets the Unicode allocation method. /// The secure string. /// A memory handle. public virtual IntPtr AllocSecureStringUni(SecureString secureString) => StringHelper.AllocSecureString(secureString, CharSet.Unicode, AllocMem); /// Gets the Ansi string allocation method. /// The value. /// A memory handle. public virtual IntPtr AllocStringAnsi(string value) => StringHelper.AllocString(value, CharSet.Ansi, AllocMem); /// Gets the Unicode string allocation method. /// The value. /// A memory handle. public virtual IntPtr AllocStringUni(string value) => StringHelper.AllocString(value, CharSet.Unicode, AllocMem); /// Frees the memory associated with a handle. /// A memory handle. public abstract void FreeMem(IntPtr hMem); /// Gets the Ansi free method. /// A memory handle. public virtual void FreeSecureStringAnsi(IntPtr hMem) => FreeMem(hMem); /// Gets the Unicode free method. /// A memory handle. public virtual void FreeSecureStringUni(IntPtr hMem) => FreeMem(hMem); /// Locks the memory of a specified handle and gets a pointer to it. /// A memory handle. /// A pointer to the locked memory. public virtual IntPtr LockMem(IntPtr hMem) => hMem; /// Gets the reallocation method. /// A memory handle. /// The size, in bytes, of memory to allocate. /// A memory handle. public virtual IntPtr ReAllocMem(IntPtr hMem, int size) { var hNew = AllocMem(size); hMem.CopyTo(hNew, size); FreeMem(hMem); return hNew; } /// Unlocks the memory of a specified handle. /// A memory handle. public virtual void UnlockMem(IntPtr hMem) { } } /// 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); #if DEBUG /// Dumps memory to byte string. [ExcludeFromCodeCoverage] public string Dump => Size == 0 ? "" : string.Join(" ", GetBytes(0, Size).Select(b => b.ToString("X2")).ToArray()); #endif /// Gets or sets the size in bytes of the allocated memory block. /// The size in bytes of the allocated memory block. public abstract SizeT Size { get; set; } /// Performs an explicit conversion from to pointer. /// The instance. /// The result of the conversion. public static unsafe explicit operator byte*(SafeAllocatedMemoryHandle hMem) => (byte*)hMem.handle; /// Performs an explicit conversion from to . /// The instance. /// The result of the conversion. public static explicit operator SafeBuffer(SafeAllocatedMemoryHandle hMem) => new SafeBufferImpl(hMem); /// Performs an implicit conversion from to . /// The instance. /// The result of the conversion. public static implicit operator IntPtr(SafeAllocatedMemoryHandle hMem) => hMem.handle; #if ALLOWSPAN /// Creates a new span over this allocated memory. /// The span representation of the structure. public virtual ReadOnlySpan AsReadOnlySpan(int length) => handle.AsReadOnlySpan(length, 0, Size); /// Creates a new span over this allocated memory. /// The span representation of the structure. public virtual Span AsSpan(int length) => handle.AsSpan(length, 0, Size); /// Casts this allocated memory to a Span<Byte>. /// A span of type . public virtual Span AsBytes() => AsSpan(Size); #endif /// 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)); handle.FillMemory(value, length); } /// Releases the owned handle without releasing the allocated memory and returns a pointer to the current memory. /// A pointer to the currently allocated memory. The caller now has the responsibility to free this memory. public virtual IntPtr TakeOwnership() { var h = handle; SetHandleAsInvalid(); handle = IntPtr.Zero; Size = 0; return h; } /// Zero out all allocated memory. public virtual void Zero() => Fill(0, Size); /// 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. public byte[] GetBytes(int startIndex, int count) { if (startIndex < 0 || startIndex + count > Size) throw new ArgumentOutOfRangeException(); var ret = new byte[count]; Marshal.Copy(handle.Offset(startIndex), ret, 0, count); return ret; } private class SafeBufferImpl : SafeBuffer { public SafeBufferImpl(SafeAllocatedMemoryHandle hMem) : base(false) => Initialize((ulong)hMem.Size); protected override bool ReleaseHandle() => true; } } /// 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 static readonly TMem mm = new TMem(); /// The number of bytes currently allocated. protected SizeT 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(SizeT size = default) : base(IntPtr.Zero, true) { if (size == 0) return; InitFromSize(size); Zero(); } /// 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, SizeT 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) : base(IntPtr.Zero, true) { if ((bytes?.Length ?? 0) == 0) return; InitFromSize(bytes.Length); Marshal.Copy(bytes, 0, handle, bytes.Length); } /// /// Initializes a new instance of the class from a /// instance, copying all the memory. /// /// The source memory block. protected SafeMemoryHandle(SafeAllocatedMemoryHandle source) : base(IntPtr.Zero, true) { if (source is null) return; InitFromSize(source.Size); ((IntPtr)source).CopyTo(handle, source.Size); } /// 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 SizeT Size { get => sz; set { if (value == 0) { ReleaseHandle(); } else { RuntimeHelpers.PrepareConstrainedRegions(); handle = IsInvalid ? mm.AllocMem(value) : mm.ReAllocMem(handle, value); if (value > sz) handle.Offset(sz).FillMemory(0, value - sz); sz = value; } } } /// 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; } private void InitFromSize(SizeT size) { RuntimeHelpers.PrepareConstrainedRegions(); SetHandle(mm.AllocMem(sz = size)); } } /// 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(SizeT 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, SizeT 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, s.GetByteCount(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, sz); } /// /// 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, sz); } /// 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) => ToString(len, 0, charSet); /// Returns a that represents this instance. /// The length. /// Number of bytes preceding the string pointer. /// The character set of the string. /// A that represents this instance. public string ToString(int len, int prefixBytes, CharSet charSet = CharSet.Unicode) { var str = StringHelper.GetString(handle.Offset(prefixBytes), charSet, sz == 0 ? long.MaxValue : sz - prefixBytes); return len == -1 ? str : str.Substring(0, Math.Min(len, str.Length)); } /// /// 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] : handle.ToStringEnum(count, charSet, prefixBytes, sz); /// /// 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] : handle.ToStringEnum(charSet, prefixBytes, sz); /// /// 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. /// Number of bytes preceding the structure. /// A managed object that contains the data that this holds. public T ToStructure(int prefixBytes = 0) { if (IsInvalid) return default; return handle.ToStructure(sz, prefixBytes); } /// Marshals data from a managed list of specified type to an offset within this allocated memory. /// /// A type of the enumerated managed object that holds the data to be marshaled. The object must be a structure or an instance of a /// formatted class. /// /// The enumerated list of items to marshal. /// /// if set to true automatically extend the allocated memory to the size required to hold . /// /// The number of bytes to skip before writing the first element of . public void Write(IEnumerable items, bool autoExtend = true, int offset = 0) { if (IsInvalid) throw new MemberAccessException("Safe memory pointer is not valid."); if (autoExtend) { var count = items?.Count() ?? 0; if (count == 0) return; InteropExtensions.TrueType(typeof(T), out var iSz); var reqSz = iSz * count + offset; if (sz < reqSz) Size = reqSz; } handle.Write(items, offset, sz); } /// Writes the specified value to an offset within this allocated memory. /// The type of the value to write. /// The value to write. /// /// if set to true automatically extend the allocated memory to the size required to hold . /// /// The number of bytes to offset from the beginning of this allocated memory before writing. public void Write(in T value, bool autoExtend = true, int offset = 0) where T : struct { if (IsInvalid) throw new MemberAccessException("Safe memory pointer is not valid."); if (autoExtend) { InteropExtensions.TrueType(typeof(T), out var iSz); var reqSz = iSz + offset; if (sz < reqSz) Size = reqSz; } handle.Write(value, offset, sz); } /// Writes the specified value to an offset within this allocated memory. /// The value to write. /// /// if set to true automatically extend the allocated memory to the size required to hold . /// /// The number of bytes to offset from the beginning of this allocated memory before writing. public void Write(object value, bool autoExtend = true, int offset = 0) { if (IsInvalid) throw new MemberAccessException("Safe memory pointer is not valid."); if (value is null) return; if (autoExtend) { InteropExtensions.TrueType(value.GetType(), out var iSz); var reqSz = iSz + offset; if (sz < reqSz) Size = reqSz; } handle.Write(value, offset, sz); } /// 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; } #if ALLOWSPAN /// Gets a reference to a structure based on this allocated memory. /// A referenced structure. public virtual ref T AsRef() => ref MemoryMarshal.GetReference(AsSpan(1)); #endif } }