using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Security; using System.Security.Permissions; using Vanara.Extensions.Reflection; using Vanara.InteropServices; using Vanara.PInvoke; using Vanara.PInvoke.Collections; namespace Vanara.Extensions { /// Extension methods for System.Runtime.InteropServices. public static partial class InteropExtensions { /// /// Aligns the specified pointer to an adjacent memory location that can be accessed by a adding a constant and its multiples. /// /// The pointer to align. /// The aligned pointer. This value may be the same as . public static IntPtr Align(this IntPtr ptr) => new IntPtr((ptr.ToInt64() + IntPtr.Size - 1) & (~(((long)IntPtr.Size) - 1))); #if ALLOWSPAN /// Returns the pointer as a . /// The type of items in the . /// A pointer to the starting address of a specified number of elements in memory. /// The number of elements to be included in the . /// Bytes to skip before starting the span. /// If known, the total number of bytes allocated to the native memory in . /// A that represents the memory. /// public static unsafe ReadOnlySpan AsReadOnlySpan(this IntPtr ptr, int length, int prefixBytes = 0, SizeT allocatedBytes = default) { if (ptr == IntPtr.Zero) return null; if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); if (allocatedBytes > 0 && SizeOf() * length + prefixBytes > allocatedBytes) throw new InsufficientMemoryException(); return new ReadOnlySpan((ptr + prefixBytes).ToPointer(), length); } /// Gets a reference to a structure based on this allocated memory. /// The type of items in the . /// A pointer to the starting address of a specified number of elements in memory. /// Bytes to skip before starting the span. /// If known, the total number of bytes allocated to the native memory in . /// A referenced structure. public static ref T AsRef(this IntPtr ptr, int prefixBytes = 0, SizeT allocatedBytes = default) => ref MemoryMarshal.GetReference(AsSpan(ptr, 1, prefixBytes, allocatedBytes)); /// Returns the pointer as a . /// The type of items in the . /// A pointer to the starting address of a specified number of elements in memory. /// The number of elements to be included in the . /// Bytes to skip before starting the span. /// If known, the total number of bytes allocated to the native memory in . /// A that represents the memory. /// public static unsafe Span AsSpan(this IntPtr ptr, int length, int prefixBytes = 0, SizeT allocatedBytes = default) { if (ptr == IntPtr.Zero) return null; if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); if (allocatedBytes > 0 && SizeOf() * length + prefixBytes > allocatedBytes) throw new InsufficientMemoryException(); return new Span((ptr + prefixBytes).ToPointer(), length); } #endif /// Returns the pointer. /// The type of items. /// A pointer to the starting address of a specified number of elements in memory. /// The number of elements to be included in the pointer. /// Bytes to skip before starting the span. /// If known, the total number of bytes allocated to the native memory in . /// A pointer that represents the memory. /// public static unsafe T* AsUnmanagedArrayPointer(this IntPtr ptr, int length, int prefixBytes = 0, SizeT allocatedBytes = default) where T : unmanaged { if (ptr == IntPtr.Zero) return null; if (length < 0) throw new ArgumentOutOfRangeException(nameof(length)); if (allocatedBytes > 0 && SizeOf() * length + prefixBytes > allocatedBytes) throw new InsufficientMemoryException(); return (T*)ptr.Offset(prefixBytes).ToPointer(); } /// Copies the number of specified bytes from one unmanaged memory block to another. /// The allocated memory pointer. /// The allocated memory pointer to copy to. /// The number of bytes to copy from to . public static void CopyTo(this IntPtr ptr, IntPtr dest, long length) => CopyTo(ptr, 0L, dest, length); /// Copies the number of specified bytes from one unmanaged memory block to another. /// The allocated memory pointer. /// The offset from at which to start the copying. /// The allocated memory pointer to copy to. /// The number of bytes to copy from to . public static void CopyTo(this IntPtr source, long start, IntPtr dest, long length) { if (start < 0 || length < 0) throw new ArgumentOutOfRangeException(); if (source == IntPtr.Zero || dest == IntPtr.Zero) throw new ArgumentNullException(); unsafe { byte* psrc = (byte*)source + start, pdest = (byte*)dest; for (long i = 0; i < length; i++, psrc++, pdest++) *pdest = *psrc; } } /// /// Fills the memory with a particular byte value. This is a very dangerous function that can cause memory /// access errors if the provided is bigger than allocated memory of if the is not a /// valid memory pointer. /// /// The allocated memory pointer. /// The byte value with which to fill the memory. /// The number of bytes to fill with the value. public static void FillMemory(this IntPtr ptr, byte value, long length) { if (ptr == IntPtr.Zero || length <= 0) return; // Write multiples of 8 bytes first var lval = value == 0 ? 0L : BitConverter.ToInt64(new[] { value, value, value, value, value, value, value, value }, 0); for (var ofs = 0L; ofs < length / 8; ofs++) Marshal.WriteInt64(ptr.Offset(ofs * 8), 0, lval); // Write remaining bytes for (var ofs = length - (length % 8); ofs < length; ofs++) Marshal.WriteByte(ptr.Offset(ofs), 0, value); } /// Converts an that points to a C-style array into an . /// Type of native structure used by the C-style array. /// The pointing to the native array. /// The number of items in the native array. /// Bytes to skip before reading the array. /// If known, the total number of bytes allocated to the native memory in . /// An exposing the elements of the native array. public static IEnumerator GetEnumerator(this IntPtr ptr, int count, int prefixBytes = 0, SizeT allocatedBytes = default) => new NativeMemoryEnumerator(ptr, count, prefixBytes, allocatedBytes); /// Converts an that points to a C-style array into an . /// The pointing to the native array. /// Type of native structure used by the C-style array. /// The number of items in the native array. /// Bytes to skip before reading the array. /// If known, the total number of bytes allocated to the native memory in . /// An exposing the elements of the native array. public static System.Collections.IEnumerator GetEnumerator(this IntPtr ptr, Type type, int count, int prefixBytes = 0, SizeT allocatedBytes = default) => new UntypedNativeMemoryEnumerator(ptr, type, count, prefixBytes, allocatedBytes); /// /// Gets the length of a null terminated array of pointers. This is a very dangerous function and can result in /// memory access errors if the does not point to a null-terminated array of pointers. /// /// The pointing to the native array. /// /// The number of non-null pointers in the array. If is equal to IntPtr.Zero, this result is 0. /// public static int GetNulledPtrArrayLength(this IntPtr lptr) { if (lptr == IntPtr.Zero) return 0; var c = 0; while (Marshal.ReadIntPtr(lptr, IntPtr.Size * c++) != IntPtr.Zero) { } return c - 1; } /// Determines whether this type is formatted or blittable. /// The type to check. /// if the specified type is blittable; otherwise, . public static bool IsBlittable(this Type T) { if (T is null) return false; // if (T.IsArray && T.GetArrayRank() > 1) return false; // Need to find a way to exclude jagged arrays while (T.IsArray) T = T.GetElementType(); // //if (T == typeof(decimal) || T.IsAbstract || T.IsAutoClass || T.IsGenericType) return false; //if (T.IsEnum || T.IsPrimitive && T != typeof(bool) && T != typeof(char)) return true; //try //{ // GCHandle.Alloc(FormatterServices.GetUninitializedObject(T), GCHandleType.Pinned).Free(); // return true; //} //catch //{ // return false; //} if (T.IsEnum) return true; try { Marshal.SizeOf(T); return true; } catch { return false; } } /// Determines whether this type is marshalable. /// The type to check. /// if the specified type is marshalable; otherwise, . public static bool IsMarshalable(this Type type) { var t = type.IsNullable() ? type.GetGenericArguments()[0] : type; return t.IsSerializable || VanaraMarshaler.CanMarshal(t, out _) || t.IsBlittable(); } /// Determines whether this type is nullable (derived from ). /// The type to check. /// if the specified type is nullable; otherwise, . public static bool IsNullable(this Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); /// Marshals an unmanaged linked list of structures to an of that structure. /// Type of native structure used by the unmanaged linked list. /// The pointing to the native array. /// The expression to be used to fetch the pointer to the next item in the list. /// An exposing the elements of the linked list. public static IEnumerable LinkedListToIEnum(this IntPtr ptr, Func next) { for (var pCurrent = ptr; pCurrent != IntPtr.Zero;) { var ret = pCurrent.ToStructure(); yield return ret; pCurrent = next(ret); } } /// Marshals an unmanaged linked list of structures to an of that structure. /// Type of native structure used by the unmanaged linked list. /// The pointing to the native array. /// The expression to be used to fetch the offset from the current pointer to the next item in the list. /// /// The number of allocated bytes behind . This value is used to determine when to stop enumerating. /// /// An exposing the elements of the linked list. public static IEnumerable LinkedListToIEnum(this IntPtr ptr, Func nextOffset, SizeT allocatedBytes) { var pEnd = ptr.Offset(allocatedBytes); for (var pCurrent = ptr; pCurrent.ToInt64() < pEnd.ToInt64();) { var ret = pCurrent.ToStructure(); yield return ret; pCurrent = pCurrent.Offset(nextOffset(ret)); } } /// /// Marshals data from a managed list of objects to an unmanaged block of memory allocated by the method. /// /// The enumerated list of objects to marshal. /// /// The function that allocates the memory for the block of objects (typically or . /// /// The bytes allocated by the method. /// /// if set to the pointer will be processed by storing a reference to the value; if , /// the pointer value will be directly inserted into the array of pointers. /// /// The character set to use for strings. /// Number of bytes preceding the allocated objects. /// Pointer to the allocated native (unmanaged) array of objects stored using the character set defined by . public static IntPtr MarshalObjectsToPtr(this IEnumerable values, Func memAlloc, out int bytesAllocated, bool referencePointers = false, CharSet charSet = CharSet.Auto, int prefixBytes = 0) { // Bail early if empty if (values is null || !values.Any()) { bytesAllocated = prefixBytes + IntPtr.Size; var ret = memAlloc(bytesAllocated); ret.FillMemory(0, bytesAllocated); return ret; } // Write to memory stream using (var ms = new NativeMemoryStream(1024, 1024) { CharSet = charSet }) { ms.SetLength(ms.Position = prefixBytes); foreach (var o in values) { if (referencePointers) ms.WriteReferenceObject(o); else ms.WriteObject(o); } if (referencePointers) ms.WriteReference(null); ms.Flush(); // Copy to newly allocated memory using memAlloc bytesAllocated = (int)ms.Length; var ret = memAlloc(bytesAllocated); ms.Pointer.CopyTo(ret, bytesAllocated); return ret; } } /// Marshals data from a managed list of specified type to a pre-allocated unmanaged block of 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. /// /// A pointer to a pre-allocated block of memory. The allocated memory must be sufficient to hold the size of /// times the number of items in the enumeration plus the number of bytes specified by . /// /// The number of bytes to skip before writing the first element of . [Obsolete("Please use the Vanara.Extensions.InteropExtensions.Write method instead. This will be removed from the library shortly as it performs no allocation.", true)] public static void MarshalToPtr(this IEnumerable items, IntPtr ptr, int prefixBytes = 0) => Write(ptr, items, prefixBytes); /// Marshals data from a managed object to an unmanaged block of memory that is allocated using . /// The type of the managed object. /// /// A managed object that holds the data to be marshaled. The object must be a structure or an instance of a formatted class. /// /// /// The function that allocates the memory for the structure (typically or . /// /// The bytes allocated by the method. /// Number of bytes preceding the trailing strings. /// /// The function used to lock memory before assignment. If , the result from will /// be used. /// /// The optional function to unlock memory after assignment. /// A pointer to the memory allocated by . public static IntPtr MarshalToPtr(this T value, Func memAlloc, out int bytesAllocated, int prefixBytes = 0, Func memLock = null, Action memUnlock = null) { memLock ??= Passthrough; if (VanaraMarshaler.CanMarshal(typeof(T), out var marshaler)) { using var mem = marshaler.MarshalManagedToNative(value); var ret = memAlloc(bytesAllocated = mem.Size + prefixBytes); mem.DangerousGetHandle().CopyTo(memLock(ret).Offset(prefixBytes), mem.Size); memUnlock?.Invoke(ret); return ret; } else { var newVal = TrueValue(value, out bytesAllocated); bytesAllocated += prefixBytes; var ret = memAlloc(bytesAllocated); Write(memLock(ret), newVal, prefixBytes, bytesAllocated); memUnlock?.Invoke(ret); return ret; } } private static IntPtr Passthrough(IntPtr p) => p; /// /// Marshals data from a managed list of specified type to an unmanaged block of memory allocated by the method. /// /// /// 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. /// /// The function that allocates the memory for the block of items (typically or . /// /// The bytes allocated by the method. /// Number of bytes preceding the trailing strings. /// /// The function used to lock memory before assignment. If , the result from will /// be used. /// /// The optional function to unlock memory after assignment. /// Pointer to the allocated native (unmanaged) array of items stored. /// Structure layout is not sequential or explicit. public static IntPtr MarshalToPtr(this IEnumerable items, Func memAlloc, out int bytesAllocated, int prefixBytes = 0, Func memLock = null, Action memUnlock = null) { if (!typeof(T).IsMarshalable()) throw new ArgumentException(@"Structure layout is not sequential or explicit."); memLock ??= Passthrough; bytesAllocated = prefixBytes; var count = items?.Count() ?? 0; if (count == 0) return memAlloc(bytesAllocated); var sz = Marshal.SizeOf(typeof(T)); bytesAllocated += sz * count; var result = memAlloc(bytesAllocated); memLock(result).Write(items, prefixBytes, bytesAllocated); memUnlock?.Invoke(result); return result; } /// /// Marshals data from an array of a specified type to an unmanaged block of memory allocated by the method. /// /// /// A type of the array element that holds the data to be marshaled. The object must be a structure or an instance of a formatted class. /// /// The array of items to marshal. /// /// The function that allocates the memory for the block of items (typically or . /// /// The bytes allocated by the method. /// Number of bytes preceding the trailing strings. /// /// The function used to lock memory before assignment. If , the result from will /// be used. /// /// The optional function to unlock memory after assignment. /// Pointer to the allocated native (unmanaged) array of items stored. /// Structure layout is not sequential or explicit. public static IntPtr MarshalToPtr(this T[] items, Func memAlloc, out int bytesAllocated, int prefixBytes = 0, Func memLock = null, Action memUnlock = null) => MarshalToPtr(items.Cast(), memAlloc, out bytesAllocated, prefixBytes, memLock, memUnlock); /// /// Marshals data from a managed list of strings to an unmanaged block of memory allocated by the method. /// /// The enumerated list of strings to marshal. /// The packing type for the strings. /// /// The function that allocates the memory for the block of strings (typically or . /// /// The bytes allocated by the method. /// The character set to use for the strings. /// Number of bytes preceding the trailing strings. /// /// The function used to lock memory before assignment. If , the result from will /// be used. /// /// The optional function to unlock memory after assignment. /// /// Pointer to the allocated native (unmanaged) array of strings stored using the model and the character /// set defined by . /// public static IntPtr MarshalToPtr(this IEnumerable values, StringListPackMethod packing, Func memAlloc, out int bytesAllocated, CharSet charSet = CharSet.Auto, int prefixBytes = 0, Func memLock = null, Action memUnlock = null) { memLock ??= Passthrough; // Bail early if empty if (values is null || !values.Any()) { bytesAllocated = prefixBytes + (packing == StringListPackMethod.Concatenated ? StringHelper.GetCharSize(charSet) : IntPtr.Size); var ret = memAlloc(bytesAllocated); memLock(ret).FillMemory(0, bytesAllocated); memUnlock?.Invoke(ret); return ret; } // Write to memory stream using (var ms = new NativeMemoryStream(1024, 1024) { CharSet = charSet }) { ms.SetLength(ms.Position = prefixBytes); if (packing == StringListPackMethod.Packed) { foreach (var s in values) ms.WriteReference(s); ms.WriteReference(null); } else { foreach (var s in values) { if (string.IsNullOrEmpty(s)) throw new ArgumentException("Concatenated string arrays cannot contain empty or null strings."); ms.Write(s); } ms.Write(""); } ms.Flush(); // Copy to newly allocated memory using memAlloc bytesAllocated = (int)ms.Length; var ret = memAlloc(bytesAllocated); ms.Pointer.CopyTo(memLock(ret), bytesAllocated); memUnlock?.Invoke(ret); return ret; } } /// /// Marshals data from a managed array of strings to an unmanaged block of memory allocated by the method. /// /// The array of strings to marshal. /// The packing type for the strings. /// /// The function that allocates the memory for the block of strings (typically or . /// /// The bytes allocated by the method. /// The character set to use for the strings. /// Number of bytes preceding the trailing strings. /// /// The function used to lock memory before assignment. If , the result from will /// be used. /// /// The optional function to unlock memory after assignment. /// /// Pointer to the allocated native (unmanaged) array of strings stored using the model and the character /// set defined by . /// public static IntPtr MarshalToPtr(this string[] values, StringListPackMethod packing, Func memAlloc, out int bytesAllocated, CharSet charSet = CharSet.Auto, int prefixBytes = 0, Func memLock = null, Action memUnlock = null) => MarshalToPtr((IEnumerable)values, packing, memAlloc, out bytesAllocated, charSet, prefixBytes, memLock, memUnlock); /// Adds an offset to the value of a pointer. /// The pointer to add the offset to. /// The offset to add. /// A new pointer that reflects the addition of to . public static IntPtr Offset(this IntPtr pointer, long offset) => new IntPtr(pointer.ToInt64() + offset); /// Queries the object for a COM interface and returns it, if found, in . /// The object to query. /// The interface identifier (IID) of the requested interface. /// When this method returns, contains a reference to the returned interface. /// An HRESULT that indicates the success or failure of the call. public static int QueryInterface(object iUnk, Guid iid, out object ppv) { var hr = Marshal.QueryInterface(Marshal.GetIUnknownForObject(iUnk), ref iid, out var ippv); ppv = hr == 0 ? Marshal.GetObjectForIUnknown(ippv) : null; return hr; } /// Returns the native memory size of a type, if possible. /// The type whose size is to be returned. /// The size, in bytes, of the type that is specified by the type parameter. /// Unable to get size of type. public static SizeT SizeOf() => SizeOf(typeof(T)); /// Returns the native memory size of a type, if possible. /// The type whose size is to be returned. /// The size, in bytes, of the type that is specified by the parameter. /// Unable to get size of type. - type public static SizeT SizeOf(Type type) { if (VanaraMarshaler.CanMarshal(type, out var marshaler)) return marshaler.GetNativeSize(); return type.IsEnum ? Marshal.SizeOf(Enum.GetUnderlyingType(type)) : Marshal.SizeOf(type); } /// Marshals data from a managed object to an unmanaged block of memory that is allocated using . /// The type of the managed object. /// /// A managed object that holds the data to be marshaled. The object must be a structure or an instance of a formatted class. /// /// /// The function that allocates the memory for the structure (typically or . /// /// The bytes allocated by the method. /// A pointer to the memory allocated by . [Obsolete("This function has been renamed MarshalToPtr for consistency. Please migrate your usage as this method will be removed in subsequent releases.")] public static IntPtr StructureToPtr(this T value, Func memAlloc, out int bytesAllocated) => MarshalToPtr(value, memAlloc, out bytesAllocated); /// Converts an that points to a C-style array into a CLI array. /// Type of native structure used by the C-style array. /// The pointing to the native array. /// The number of items in the native array. /// Bytes to skip before reading the array. /// If known, the total number of bytes allocated to the native memory in . /// An array of type containing the elements of the native array. public static T[] ToArray(this IntPtr ptr, int count, int prefixBytes = 0, SizeT allocatedBytes = default) => ToArray(ptr, typeof(T), count, prefixBytes, allocatedBytes).ToTypedArray(); /// Converts an that points to a C-style array into a CLI array. /// The pointing to the native array. /// Type of native structure used by the C-style array. /// The number of items in the native array. /// Bytes to skip before reading the array. /// If known, the total number of bytes allocated to the native memory in . /// An array of type containing the elements of the native array. public static Array ToArray(this IntPtr ptr, Type type, int count, int prefixBytes = 0, SizeT allocatedBytes = default) { if (type is null) throw new ArgumentNullException(nameof(type)); if (ptr == IntPtr.Zero) return null; var ret = Array.CreateInstance(type, count); // new object[count]; var stSize = SizeOf(type); if (allocatedBytes > 0 && stSize * count + prefixBytes > allocatedBytes) throw new InsufficientMemoryException(); if (allocatedBytes == default) allocatedBytes = uint.MaxValue; for (var i = 0; i < count; i++) { var offset = prefixBytes + i * stSize; ret.SetValue(ptr.Offset(offset).Convert(allocatedBytes - (uint)offset, type), i); } return ret; } /// Converts an that points to a C-style array into an . /// Type of native structure used by the C-style array. /// The pointing to the native array. /// The number of items in the native array. /// Bytes to skip before reading the array. /// If known, the total number of bytes allocated to the native memory in . /// An exposing the elements of the native array. public static IEnumerable ToIEnum(this IntPtr ptr, int count, int prefixBytes = 0, SizeT allocatedBytes = default) => new NativeMemoryEnumerator(ptr, count, prefixBytes, allocatedBytes); /// Converts an that points to a C-style array into an . /// The pointing to the native array. /// Type of native structure used by the C-style array. /// The number of items in the native array. /// Bytes to skip before reading the array. /// If known, the total number of bytes allocated to the native memory in . /// An exposing the elements of the native array. public static System.Collections.IEnumerable ToIEnum(this IntPtr ptr, Type type, int count, int prefixBytes = 0, SizeT allocatedBytes = default) => new UntypedNativeMemoryEnumerator(ptr, type, count, prefixBytes, allocatedBytes); /// Converts a to a string. /// The value. /// The extracted string. public static string ToInsecureString(this SecureString s) { if (s == null) return null; var p = IntPtr.Zero; try { p = Marshal.SecureStringToCoTaskMemUnicode(s); return Marshal.PtrToStringUni(p); } finally { if (p != IntPtr.Zero) Marshal.ZeroFreeCoTaskMemUnicode(p); } } /// Converts a to a . /// The . /// An equivalent . public static IntPtr ToIntPtr(this UIntPtr p) { unsafe { return new IntPtr(p.ToPointer()); } } /// Converts an to a structure. If pointer has no value, null is returned. /// Type of the structure. /// The that points to allocated memory holding a structure or . /// The converted structure or null. public static T? ToNullableStructure(this IntPtr ptr) where T : struct => ptr != IntPtr.Zero ? ptr.ToStructure() : (T?)null; /// Converts a pointer to an unmanaged Unicode string to a . /// A pointer to an unmanaged Unicode string. /// A with the contents of the in memory string. public static SecureString ToSecureString(this IntPtr p) { if (p == IntPtr.Zero) return null; var s = new SecureString(); var i = 0; while (true) { var c = (char)Marshal.ReadInt16(p, ((i++) * sizeof(short))); if (c == '\u0000') break; s.AppendChar(c); } s.MakeReadOnly(); return s; } /// Converts a pointer to an unmanaged Unicode string of a specified length to a . /// A pointer to an unmanaged Unicode string. /// The number of Unicode characters in the unmanaged string, excluding any terminating null values. /// A with the contents of the in memory string. public static SecureString ToSecureString(this IntPtr p, int length) { if (p == IntPtr.Zero) return null; var s = new SecureString(); for (var i = 0; i < length; i++) s.AppendChar((char)Marshal.ReadInt16(p, i * sizeof(short))); s.MakeReadOnly(); return s; } /// Converts a string to a . /// A string. /// A with the contents of the string. public static SecureString ToSecureString(this string s) { if (s == null) return null; var ss = new SecureString(); foreach (var c in s) ss.AppendChar(c); ss.MakeReadOnly(); return ss; } /// /// Returns an enumeration of strings from memory where each string is pointed to by a preceding list of pointers of length . /// /// The pointing to the native array. /// The count of expected strings. /// The character set of the strings. /// Number of bytes preceding the array of string pointers. /// If known, the total number of bytes allocated to the native memory in . /// Enumeration of strings. public static IEnumerable ToStringEnum(this IntPtr ptr, int count, CharSet charSet = CharSet.Auto, int prefixBytes = 0, SizeT allocatedBytes = default) { if (ptr == IntPtr.Zero || count == 0) yield break; if (allocatedBytes > 0 && count * IntPtr.Size + prefixBytes > allocatedBytes) throw new InsufficientMemoryException(); for (var i = 0; i < count; i++) { var sptr = Marshal.ReadIntPtr(ptr.Offset(prefixBytes + i * IntPtr.Size)); yield return StringHelper.GetString(sptr, charSet); } } /// /// 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 pointing to the native array. /// The character set of the strings. /// Number of bytes preceding the array of string pointers. /// If known, the total number of bytes allocated to the native memory in . /// An enumerated list of strings. public static IEnumerable ToStringEnum(this IntPtr lptr, CharSet charSet = CharSet.Auto, int prefixBytes = 0, SizeT allocatedBytes = default) { if (lptr == IntPtr.Zero) yield break; var charLength = StringHelper.GetCharSize(charSet); var i = prefixBytes; if (allocatedBytes == 0) allocatedBytes = SizeT.MaxValue; for (var ptr = lptr.Offset(i); i + charLength <= allocatedBytes && GetCh(ptr) != 0; i += charLength, ptr = lptr.Offset(i)) { for (var cptr = ptr; i + charLength <= allocatedBytes && GetCh(cptr) != 0; cptr = cptr.Offset(charLength), i += charLength) { } if (i + charLength > allocatedBytes) throw new InsufficientMemoryException(); yield return StringHelper.GetString(ptr, charSet); //ptr = ptr.Offset(((s?.Length ?? 0) + 1) * charLength); } if (i + charLength > allocatedBytes) throw new InsufficientMemoryException(); int GetCh(IntPtr p) { return charLength == 1 ? Marshal.ReadByte(p) : Marshal.ReadInt16(p); } } /// /// Marshals data from an unmanaged 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 pointer to an unmanaged block of memory. /// If known, the total number of bytes allocated to the native memory in . /// The number of bytes to skip before reading the element. /// A managed object that contains the data that the parameter points to. /// [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static T ToStructure(this IntPtr ptr, SizeT allocatedBytes = default, int offset = 0) { if (allocatedBytes == default) allocatedBytes = uint.MaxValue; return ptr.Offset(offset).Convert(allocatedBytes - (uint)offset); } /// Marshals data from an unmanaged block of memory to a managed object. /// The type of the object to which the data is to be copied. This must be a formatted class. /// A pointer to an unmanaged block of memory. /// The object to which the data is to be copied. This must be an instance of a formatted class. /// If known, the total number of bytes allocated to the native memory in . /// The number of bytes to skip before reading the element. /// A managed object that contains the data that the parameter points to. public static void ToStructure(this IntPtr ptr, T instance, SizeT allocatedBytes = default, int offset = 0) where T : class { if (ptr == IntPtr.Zero) throw new NullReferenceException(); var t = TrueType(typeof(T), out var stSize); if (allocatedBytes > 0 && allocatedBytes < stSize + offset) throw new InsufficientMemoryException(); if (t == typeof(T)) Marshal.PtrToStructure(ptr, instance); else using (var pin = new PinnedObject(instance)) ((IntPtr)pin).Write(Marshal.PtrToStructure(ptr.Offset(offset), t)); } /// Converts a single-dimensional to an array of . /// /// The type of the output array. All elements in the array supplied as must be of this type. /// /// The input array. /// An array of elements. public static T[] ToTypedArray(this Array input) => input?.Cast().ToArray(); /// Converts a to a . /// The . /// An equivalent . public static UIntPtr ToUIntPtr(this IntPtr p) { unsafe { return new UIntPtr(p.ToPointer()); } } /// Converts an unsafe structure pointer into a managed array. /// Type of native structure used by the C-style array. /// The pointer to the first structure in the native array. /// The number of items in the native array. /// If known, the total number of bytes allocated to the native memory in . /// An array of type containing the elements of the native array. public static unsafe T[] UnsafePtrToArray(T* ptr, int count, SizeT allocatedBytes = default) where T : unmanaged { var stSize = SizeOf(); if (allocatedBytes > 0 && stSize * count > allocatedBytes) throw new InsufficientMemoryException(); if (allocatedBytes == default) allocatedBytes = uint.MaxValue; var ret = new T[count]; for (var i = 0; i < count; i++) ret[i] = ptr[i]; return ret; } /// Marshals data from a managed list of specified type to a pre-allocated unmanaged block of 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. /// /// /// A pointer to a pre-allocated block of memory. The allocated memory must be sufficient to hold the size of /// times the number of items in the enumeration plus the number of bytes specified by . /// /// The enumerated list of items to marshal. /// The number of bytes to skip before writing the first element of . /// If known, the total number of bytes allocated to the native memory in . /// The number of bytes written. The offset is not included. /// Structure layout is not sequential or explicit. /// public static int Write(this IntPtr ptr, IEnumerable items, int offset = 0, SizeT allocatedBytes = default) { var count = items?.Count() ?? 0; if (count == 0) return 0; var ttype = TrueType(typeof(T), out var stSize); if (!ttype.IsMarshalable()) throw new ArgumentException(@"Structure layout is not sequential or explicit."); var bytesReq = stSize * count + offset; if (allocatedBytes > 0 && bytesReq > allocatedBytes) throw new InsufficientMemoryException(); var i = 0; foreach (var item in items.Select(v => Convert.ChangeType(v, ttype)).Where(v => v != null)) WriteNoChecks(ptr, item, offset + i++ * stSize, allocatedBytes); return bytesReq - offset; } /// Writes the specified value to pre-allocated memory. /// The address of the memory where the value is to be written. /// The value to write. /// The number of bytes to offset from before writing. /// If known, the total number of bytes allocated to the native memory in . /// The number of bytes written. The offset is not included. /// public static int Write(this IntPtr ptr, object value, int offset = 0, SizeT allocatedBytes = default) { if (value is null) return 0; if (!value.GetType().IsMarshalable()) throw new ArgumentException(@"Value cannot be serialized to memory.", nameof(value)); return WriteNoChecks(ptr, value, offset, allocatedBytes); } /// Writes the specified value to pre-allocated memory. /// The type of the value to write. /// The address of the memory where the value is to be written. /// The value to write. /// The number of bytes to offset from before writing. /// If known, the total number of bytes allocated to the native memory in . /// The number of bytes written. The offset is not included. /// public static int Write(this IntPtr ptr, in T value, int offset = 0, SizeT allocatedBytes = default) where T : struct => WriteNoChecks(ptr, value, offset, allocatedBytes); internal static Type TrueType(Type type, out int size) { var ttype = type.IsEnum ? Enum.GetUnderlyingType(type) : type == typeof(bool) ? typeof(uint) : type; try { size = Marshal.SizeOf(ttype); } catch { size = 0; } return ttype; } internal static T GetValueType(IntPtr ptr, Type trueType = null, int offset = 0, SizeT allocatedBytes = default) => (T)GetValueType(ptr, typeof(T), trueType, offset, allocatedBytes); internal static object GetValueType(IntPtr ptr, Type type, Type trueType = null, int offset = 0, SizeT allocatedBytes = default) { if (allocatedBytes == 0) allocatedBytes = SizeT.MaxValue; trueType ??= type.IsEnum ? Enum.GetUnderlyingType(type) : type; var obj = VanaraMarshaler.CanMarshal(trueType, out var marshaler) ? marshaler.MarshalNativeToManaged(ptr.Offset(offset), allocatedBytes) : Marshal.SizeOf(trueType) <= allocatedBytes ? Marshal.PtrToStructure(ptr.Offset(offset), trueType) : throw new InsufficientMemoryException(); return type == trueType ? obj : type.IsEnum ? Enum.ToObject(type, obj) : Convert.ChangeType(obj, type); } private static object TrueValue(object value, out int size) => Convert.ChangeType(value, TrueType(value.GetType(), out size)); internal static int WriteNoChecks(IntPtr ptr, object value, int offset, SizeT allocatedBytes) { if (value is IEnumerable b) value = b.ToArray(); if (value is byte[] ba) { if (allocatedBytes > 0 && offset + ba.Length > allocatedBytes) throw new InsufficientMemoryException(); Marshal.Copy(ba, 0, ptr, ba.Length); return ba.Length; } if (VanaraMarshaler.CanMarshal(value.GetType(), out var marshaler)) { using var mem = marshaler.MarshalManagedToNative(value); if (allocatedBytes > 0 && offset + mem.Size > allocatedBytes) throw new InsufficientMemoryException(); mem.DangerousGetHandle().CopyTo(ptr.Offset(offset), mem.Size); return mem.Size; } if (value.GetType().IsBlittable()) { var newVal = TrueValue(value, out var cbValue); if (allocatedBytes > 0 && offset + cbValue > allocatedBytes) throw new InsufficientMemoryException(); Marshal.StructureToPtr(newVal, ptr.Offset(offset), false); return cbValue; } if (value.GetType().IsSerializable) { using var str = new NativeMemoryStream(); var bf = new BinaryFormatter(); bf.Serialize(str, value); str.Flush(); if (allocatedBytes > 0 && offset + str.Length > allocatedBytes) throw new InsufficientMemoryException(); str.Pointer.CopyTo(ptr.Offset(offset), str.Length); return (int)str.Length; } throw new ArgumentException("Unable to convert object to its binary format."); } } }