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