using System; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using Vanara.Extensions; using Vanara.PInvoke; namespace Vanara.InteropServices { /// /// For structures with a single array as the last field that are intended to be variable length, this class manages the structure and /// automatically marshals the correct structure to memory. /// /// The type of the structure. public class SafeAnysizeStruct : SafeAnysizeStructBase { private FieldInfo fiCount; /// Initializes a new instance of the class. /// The initial value of the structure, if provided. /// /// The name of the field in that holds the length of the array. If , the first /// public field will be selected. If "*", then the array size will be determined by the amount of allocated memory. /// /// This class can only manange sequential layout structures. public SafeAnysizeStruct(in T value, string sizeFieldName = null) : base(baseSz) { InitCountField(sizeFieldName); ToNative(value); } /// Initializes a new instance of the class from a pointer. /// A pointer to memory that holds the value of an instance of . /// The size of the allocated memory in in bytes. /// /// The name of the field in that holds the length of the array. If , the first /// public field will be selected. If "*", then the array size will be determined by the amount of allocated memory. /// public SafeAnysizeStruct(IntPtr allocatedMemory, SizeT size, string sizeFieldName = null) : base(allocatedMemory, size, false) { if (allocatedMemory == IntPtr.Zero) throw new ArgumentNullException(nameof(allocatedMemory)); if (baseSz > size) throw new OutOfMemoryException(); InitCountField(sizeFieldName); } /// Initializes a new instance of the class with an initial empty memory allocation. /// The size of the reserved memory in bytes. /// /// The name of the field in that holds the length of the array. If , the first /// public field will be selected. /// public SafeAnysizeStruct(SizeT size, string sizeFieldName = null) : base(size) => InitCountField(sizeFieldName); /// Performs an implicit conversion from to . /// The instance. /// The result of the conversion. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public static implicit operator SafeAnysizeStruct(in T s) => new(s); /// Gets the length of the array from the structure. /// The local, system marshaled, structure instance extracted from the pointer. /// The element length of the 'anysize' array. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] protected override int GetArrayLength(in T local) => fiCount is null ? GetArrLenFromSz() : (fiCount.FieldType == typeof(IntPtr) ? ((IntPtr)fiCount.GetValue(local)).ToInt32() : Convert.ToInt32(fiCount.GetValue(local))); private int GetArrLenFromSz() => 1 + (Size - baseSz) / Marshal.SizeOf(elemType); private void InitCountField(string sizeFieldName) { fiCount = string.IsNullOrEmpty(sizeFieldName) ? structType.GetOrderedFields(binds).First() : structType.GetField(sizeFieldName, binds); if (fiCount is null && sizeFieldName != "*") throw new ArgumentException("Invalid size field name.", nameof(sizeFieldName)); } } /// /// For structures with a single array as the last field that are intended to be variable length, this class manages the structure and /// automatically marshals the correct structure to memory. /// /// The type of the structure. public abstract class SafeAnysizeStructBase : SafeMemoryHandle { internal const BindingFlags binds = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; /// The base, unextended size of T. protected static readonly int baseSz; /// The type of the structure and type of the anysize array's elements. protected static readonly Type elemType, structType; /// The reflected field of the array. protected static readonly FieldInfo fiArray; static SafeAnysizeStructBase() { structType = typeof(T); if (!structType.IsLayoutSequential) throw new InvalidOperationException("This class can only manange sequential layout structures."); baseSz = Marshal.SizeOf(structType); fiArray = structType.GetOrderedFields(binds).Last(); if (!fiArray.FieldType.IsArray) throw new ArgumentException("The field information must be for an array.", nameof(fiArray)); elemType = fiArray.FieldType.FindElementType(); } /// Initializes a new instance of the class. /// The size of memory to allocate, in bytes. protected SafeAnysizeStructBase(SizeT size) : base(size) { } /// Initializes a new instance of the class. /// The allocated memory. /// The size. /// if set to [owns handle]. protected SafeAnysizeStructBase(IntPtr allocatedMemory, SizeT size, bool ownsHandle) : base(allocatedMemory, size, ownsHandle) { } /// Gets or sets the structure value. public T Value { get => FromNative(handle, Size); set => ToNative(value); } /// /// Performs an explicit conversion from to . The IntPtr is the /// memory location of the fully marshaled structure with the full final field array. /// /// The instance. /// The result of the conversion. public static implicit operator IntPtr(SafeAnysizeStructBase s) => s?.handle ?? IntPtr.Zero; /// Performs an explicit conversion from to . /// The instance. /// The result of the conversion. public static implicit operator T(SafeAnysizeStructBase s) => s is null ? default : s.Value; /// Converts the native memory to . /// The allocated memory. /// The size. /// protected virtual T FromNative(IntPtr allocatedMemory, int size) { var local = (T)Marshal.PtrToStructure(allocatedMemory, structType); // Can't use Convert or get circular ref. var cnt = GetArrayLength(local); var arrOffset = Marshal.OffsetOf(structType, fiArray.Name).ToInt32(); Array array = elemType == typeof(string) ? allocatedMemory.ToStringEnum(cnt, GetCharSet(fiArray), arrOffset, size).ToArray() : allocatedMemory.ToArray(elemType, cnt, arrOffset, size); fiArray.SetValueDirect(__makeref(local), array); return local; } /// Gets the length of the array from the structure. /// The local, system marshaled, structure instance extracted from the pointer. /// The element length of the 'anysize' array. protected abstract int GetArrayLength(in T local); /// Converts the managed instance to native. /// The managed value. protected virtual void ToNative(T value) { // Get the current array for the last field (or create one if needed) var arrVal = fiArray.GetValue(value); if (arrVal is null || ((Array)arrVal).Length == 0) { arrVal = Array.CreateInstance(elemType, 1); ((Array)arrVal).SetValue(Activator.CreateInstance(elemType), 0); fiArray.SetValueDirect(__makeref(value), arrVal); } // Determine mem required for current struct and last field value var arrLen = ((Array)arrVal).Length; if (elemType == typeof(string)) { var charSet = GetCharSet(fiArray); // Set memory size Size = baseSz + (IntPtr.Size * arrLen) + ((string[])arrVal).Sum(s => StringHelper.GetCharSize(charSet) * (s.Length + 1)); // Marshal base structure - don't use Write to prevent loops Marshal.StructureToPtr(value, handle, false); // Push each element of the array into memory, starting with second item in array since first was pushed by StructureToPtr var arrOffset = Marshal.OffsetOf(structType, fiArray.Name).ToInt32(); handle.Write((string[])arrVal, StringListPackMethod.Packed, charSet, arrOffset, Size); } else { var arrElemSz = Marshal.SizeOf(elemType); var memSz = baseSz + arrElemSz * (arrLen - 1); // Set memory size Size = memSz; // Marshal base structure - don't use Write to prevent loops Marshal.StructureToPtr(value, handle, false); // Push each element of the array into memory, starting with second item in array since first was pushed by StructureToPtr for (var i = 1; i < arrLen; i++) handle.Write(((Array)arrVal).GetValue(i), baseSz + arrElemSz * (i - 1), memSz); } } private static CharSet GetCharSet(FieldInfo fi) { if (fi.FieldType.IsArray && fi.FieldType.FindElementType() == typeof(string)) { var maa = fi.GetCustomAttribute(); if (maa != null) return fi.GetCustomAttribute().ArraySubType switch { UnmanagedType.LPWStr => CharSet.Unicode, UnmanagedType.LPTStr => CharSet.Auto, UnmanagedType.LPStr => CharSet.Ansi, _ => CharSet.Auto, }; } return fi.DeclaringType.GetCustomAttribute()?.CharSet ?? CharSet.Auto; } } /// /// A marshaler implementation of to set the marshaler as an attribute using . Use the cookie paramter of to specify the name of the field in that specifies the number of elements in the last field of . /// /// The structure type to be marshaled. /// public class SafeAnysizeStructMarshaler : IVanaraMarshaler { private readonly string sizeFieldName; /// Initializes a new instance of the class. /// /// The name of the field in that specifies the number of elements in the last field of . /// public SafeAnysizeStructMarshaler(string cookie) => sizeFieldName = cookie; SizeT IVanaraMarshaler.GetNativeSize() => Marshal.SizeOf(typeof(T)); SafeAllocatedMemoryHandle IVanaraMarshaler.MarshalManagedToNative(object managedObject) => managedObject is null ? SafeCoTaskMemHandle.Null : new SafeAnysizeStruct((T)managedObject, sizeFieldName); object IVanaraMarshaler.MarshalNativeToManaged(IntPtr pNativeData, SizeT allocatedBytes) { if (pNativeData == IntPtr.Zero) return null; using var s = new SafeAnysizeStruct(pNativeData, allocatedBytes, sizeFieldName); return s.Value; } } /// /// A marshaler implementation of to marshal structures whose last field is a character array of length /// (1) and that uses a field to determine the length of the full string. /// /// Use the cookie paramter of to specify the name of the field in that /// specifies the length of the string in the last field of along with use indicators. /// /// /// If the field specifies byte, rather than character length, follow the field name with a colon (:) followed by 'b' (for bytes) or 'c' /// (for characters). /// /// /// If the field specifies a length that does NOT include the NULL terminator, follow the field name, colon (:), and type specifier by /// 'r' (for raw) or 'n' (for null-terminated). /// /// If the field name is "*", then the string length will be determined by the amount of allocated memory. /// /// The structure type to be marshaled. public class AnySizeStringMarshaler : IVanaraMarshaler { private static readonly int charSz; private static readonly int baseSz; private readonly FieldInfo fiCount; private static readonly FieldInfo fiArray; private static readonly int strOffset; private readonly bool cntIsBytes = false; private readonly bool cntInclNull = true; private readonly bool allMem = false; static AnySizeStringMarshaler() { var structType = typeof(T); if (!structType.IsLayoutSequential) throw new InvalidOperationException("This class can only manange sequential layout structures."); baseSz = Marshal.SizeOf(structType); fiArray = structType.GetOrderedFields(SafeAnysizeStructBase.binds).Last(); if (fiArray.FieldType != typeof(string)) throw new ArgumentException("The field information must be for a string.", nameof(fiArray)); charSz = StringHelper.GetCharSize(typeof(T).StructLayoutAttribute.CharSet); strOffset = Marshal.OffsetOf(structType, fiArray.Name).ToInt32();// unchecked((uint)Marshal.ReadInt32(fiArray.FieldHandle.Value.Offset(4 + IntPtr.Size))) & 0x7FFFFFF; } /// Initializes a new instance of the class. /// /// The name of the field in that specifies the number of elements in the last field of . /// /// If the field specifies byte, rather than character length, follow the field name with a colon (:) followed by 'b' (for bytes) or /// 'c' (for characters). /// /// /// If the field specifies a length that does NOT include the NULL terminator, follow the field name, colon (:), and type specifier /// by 'r' (for raw) or 'n' (for null-terminated). /// /// public AnySizeStringMarshaler(string cookie) { if (string.IsNullOrEmpty(cookie)) fiCount = typeof(T).GetOrderedFields(SafeAnysizeStructBase.binds).First(); else if (cookie == "*") allMem = true; else { var parts = cookie.Split(':'); if (parts.Length == 2 && parts[1].Length >= 2) { cntIsBytes = parts[1][0] == 'b'; cntInclNull = parts[1].Length > 1 && parts[1][1] == 'n'; } fiCount = typeof(T).GetField(parts[0], SafeAnysizeStructBase.binds); } if (fiCount is null && !allMem) throw new ArgumentException("Invalid string length field name.", nameof(cookie)); } SizeT IVanaraMarshaler.GetNativeSize() => baseSz; SafeAllocatedMemoryHandle IVanaraMarshaler.MarshalManagedToNative(object managedObject) { var h = managedObject is null ? SafeCoTaskMemHandle.Null : new SafeCoTaskMemHandle(baseSz); var str = fiArray.GetValue(managedObject) as string; var len = allMem ? StringHelper.GetByteCount(str, true, typeof(T).StructLayoutAttribute.CharSet) : Convert.ToInt32(fiCount.GetValue(managedObject)); var clen = cntIsBytes ? len / charSz : len; if (cntInclNull && !allMem) --clen; if (str is not null && str.Length > clen) str = str.Substring(0, clen); h.Size += StringHelper.GetByteCount(str, cntInclNull, typeof(T).StructLayoutAttribute.CharSet) - charSz; Marshal.StructureToPtr(managedObject, h, false); StringHelper.Write(str, ((IntPtr)h).Offset(strOffset), out _, cntInclNull, typeof(T).StructLayoutAttribute.CharSet); return h; } object IVanaraMarshaler.MarshalNativeToManaged(IntPtr pNativeData, SizeT allocatedBytes) { if (pNativeData == IntPtr.Zero) return null; var o = Marshal.PtrToStructure(pNativeData, typeof(T)); int len; if (allMem) len = (allocatedBytes - strOffset) / charSz; else { len = Convert.ToInt32(fiCount.GetValue(o)); if (cntIsBytes) len /= charSz; if (cntInclNull) --len; } fiArray.SetValue(o, StringHelper.GetString(pNativeData.Offset(strOffset), len, typeof(T).StructLayoutAttribute.CharSet)); return o; } } }