using System; using System.Linq; using System.Runtime.InteropServices; using System.Security; using Vanara.InteropServices; namespace Vanara.Extensions { /// A safe class that represents an object that is pinned in memory. /// public static class StringHelper { /// Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters. /// The number of characters, inclusive of the null terminator. /// The method used to allocate the memory. /// The character set. /// The address of the block of memory allocated. public static IntPtr AllocChars(uint count, Func memAllocator, CharSet charSet = CharSet.Auto) { if (count == 0) return IntPtr.Zero; var sz = GetCharSize(charSet); var ptr = memAllocator((int)count * sz); if (count > 0) { if (sz == 1) Marshal.WriteByte(ptr, 0); else Marshal.WriteInt16(ptr, 0); } return ptr; } /// Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters. /// The number of characters, inclusive of the null terminator. /// The character set. /// The address of the block of memory allocated. public static IntPtr AllocChars(uint count, CharSet charSet = CharSet.Auto) => AllocChars(count, Marshal.AllocCoTaskMem, charSet); /// Copies the contents of a managed object to a block of memory allocated from the unmanaged COM task allocator. /// The managed object to copy. /// The character set. /// The address, in unmanaged memory, where the parameter was copied to, or 0 if a null object was supplied. public static IntPtr AllocSecureString(SecureString s, CharSet charSet = CharSet.Auto) { if (s == null) return IntPtr.Zero; if (GetCharSize(charSet) == 2) return Marshal.SecureStringToCoTaskMemUnicode(s); return Marshal.SecureStringToCoTaskMemAnsi(s); } /// Copies the contents of a managed object to a block of memory allocated from a supplied allocation method. /// The managed object to copy. /// The character set. /// The method used to allocate the memory. /// The address, in unmanaged memory, where the parameter was copied to, or 0 if a null object was supplied. public static IntPtr AllocSecureString(SecureString s, CharSet charSet, Func memAllocator) => AllocSecureString(s, charSet, memAllocator, out _); /// Copies the contents of a managed object to a block of memory allocated from a supplied allocation method. /// The managed object to copy. /// The character set. /// The method used to allocate the memory. /// Returns the number of allocated bytes for the string. /// The address, in unmanaged memory, where the parameter was copied to, or 0 if a null object was supplied. public static IntPtr AllocSecureString(SecureString s, CharSet charSet, Func memAllocator, out int allocatedBytes) { allocatedBytes = 0; if (s == null) return IntPtr.Zero; var chSz = GetCharSize(charSet); var encoding = chSz == 2 ? System.Text.Encoding.Unicode : System.Text.Encoding.ASCII; var hMem = AllocSecureString(s, charSet); var str = chSz == 2 ? Marshal.PtrToStringUni(hMem) : Marshal.PtrToStringAnsi(hMem); Marshal.FreeCoTaskMem(hMem); if (str == null) return IntPtr.Zero; var b = encoding.GetBytes(str); var p = memAllocator(b.Length); Marshal.Copy(b, 0, p, b.Length); allocatedBytes = b.Length; return p; } /// Copies the contents of a managed String to a block of memory allocated from the unmanaged COM task allocator. /// A managed string to be copied. /// The character set. /// The allocated memory block, or 0 if is null. public static IntPtr AllocString(string s, CharSet charSet = CharSet.Auto) => charSet == CharSet.Auto ? Marshal.StringToCoTaskMemAuto(s) : (charSet == CharSet.Unicode ? Marshal.StringToCoTaskMemUni(s) : Marshal.StringToCoTaskMemAnsi(s)); /// Copies the contents of a managed String to a block of memory allocated from a supplied allocation method. /// A managed string to be copied. /// The character set. /// The method used to allocate the memory. /// The allocated memory block, or 0 if is null. public static IntPtr AllocString(string s, CharSet charSet, Func memAllocator) => AllocString(s, charSet, memAllocator, out _); /// /// Copies the contents of a managed String to a block of memory allocated from a supplied allocation method. /// /// A managed string to be copied. /// The character set. /// The method used to allocate the memory. /// Returns the number of allocated bytes for the string. /// The allocated memory block, or 0 if is null. public static IntPtr AllocString(string s, CharSet charSet, Func memAllocator, out int allocatedBytes) { if (s == null) { allocatedBytes = 0; return IntPtr.Zero; } var b = s.GetBytes(true, charSet); var p = memAllocator(b.Length); Marshal.Copy(b, 0, p, allocatedBytes = b.Length); return p; } /// /// Zeros out the allocated memory behind a secure string and then frees that memory. /// /// The address of the memory to be freed. /// The size in bytes of the memory pointed to by . /// The memory freer. public static void FreeSecureString(IntPtr ptr, int sizeInBytes, Action memFreer) { if (IsValue(ptr)) return; var b = new byte[sizeInBytes]; Marshal.Copy(b, 0, ptr, b.Length); memFreer(ptr); } /// Frees a block of memory allocated by the unmanaged COM task memory allocator for a string. /// The address of the memory to be freed. /// The character set of the string. public static void FreeString(IntPtr ptr, CharSet charSet = CharSet.Auto) { if (IsValue(ptr)) return; if (GetCharSize(charSet) == 2) Marshal.ZeroFreeCoTaskMemUnicode(ptr); else Marshal.ZeroFreeCoTaskMemAnsi(ptr); } /// Gets the encoded bytes for a string including an optional null terminator. /// The string value to convert. /// if set to true include a null terminator at the end of the string in the resulting byte array. /// The character set. /// A byte array including encoded as per and the optional null terminator. public static byte[] GetBytes(this string value, bool nullTerm = true, CharSet charSet = CharSet.Auto) { var chSz = GetCharSize(charSet); var enc = chSz == 1 ? System.Text.Encoding.ASCII : System.Text.Encoding.Unicode; var ret = new byte[enc.GetByteCount(value) + (nullTerm ? chSz : 0)]; enc.GetBytes(value, 0, value.Length, ret, 0); if (nullTerm) enc.GetBytes(new[] { '\0' }, 0, 1, ret, ret.Length - chSz); return ret; } /// Gets the number of bytes required to store the string. /// The string value. /// if set to true include a null terminator at the end of the string in the count if does not equal null. /// The character set. /// The number of bytes required to store . Returns 0 if is null. public static int GetByteCount(this string value, bool nullTerm = true, CharSet charSet = CharSet.Auto) { if (value == null) return 0; var chSz = GetCharSize(charSet); var enc = chSz == 1 ? System.Text.Encoding.ASCII : System.Text.Encoding.Unicode; return enc.GetByteCount(value) + (nullTerm ? chSz : 0); } /// Gets the size of a character defined by the supplied . /// The character set to size. /// The size of a standard character, in bytes, from . public static int GetCharSize(CharSet charSet = CharSet.Auto) => charSet == CharSet.Auto ? Marshal.SystemDefaultCharSize : (charSet == CharSet.Unicode ? 2 : 1); /// /// Allocates a managed String and copies all characters up to the first null character or the end of the allocated memory pool from a string stored in unmanaged memory into it. /// /// The address of the first character. /// The character set of the string. /// If known, the total number of bytes allocated to the native memory in . /// /// A managed string that holds a copy of the unmanaged string if the value of the parameter is not null; /// otherwise, this method returns null. /// public static string GetString(IntPtr ptr, CharSet charSet = CharSet.Auto, long allocatedBytes = long.MaxValue) { if (IsValue(ptr)) return null; var sb = new System.Text.StringBuilder(); unsafe { var chkLen = 0L; if (GetCharSize(charSet) == 1) { for (var uptr = (byte*)ptr; chkLen < allocatedBytes && *uptr != 0; chkLen++, uptr++) sb.Append((char)*uptr); } else { for (var uptr = (ushort*)ptr; chkLen + 2 <= allocatedBytes && *uptr != 0; chkLen += 2, uptr++) sb.Append((char)*uptr); } } return sb.ToString(); } /// /// Allocates a managed String and copies all characters up to the first null character or at most characters from a string stored in unmanaged memory into it. /// /// The address of the first character. /// The number of characters to copy. /// The character set of the string. /// /// A managed string that holds a copy of the unmanaged string if the value of the parameter is not null; /// otherwise, this method returns null. /// public static string GetString(IntPtr ptr, int length, CharSet charSet = CharSet.Auto) => GetString(ptr, charSet, length * GetCharSize(charSet)); /// Indicates whether a specified string is , empty, or consists only of white-space characters. /// The string to test. /// /// if the parameter is or , or if /// value consists exclusively of white-space characters. /// public static bool IsNullOrWhiteSpace(string value) => value is null || value.All(c => char.IsWhiteSpace(c)); /// Refreshes the memory block from the unmanaged COM task allocator and copies the contents of a new managed String. /// The address of the first character. /// Receives the new character length of the allocated memory block. /// A managed string to be copied. /// The character set of the string. /// true if the memory block was reallocated; false if set to null. public static bool RefreshString(ref IntPtr ptr, out uint charLen, string s, CharSet charSet = CharSet.Auto) { FreeString(ptr, charSet); ptr = AllocString(s, charSet); charLen = s == null ? 0U : (uint)s.Length + 1; return s != null; } /// Writes the specified string to a pointer to allocated memory. /// The string value. /// The pointer to the allocated memory. /// The resulting number of bytes written. /// if set to true include a null terminator at the end of the string in the count if does not equal null. /// The character set of the string. /// If known, the total number of bytes allocated to the native memory in . public static void Write(string value, IntPtr ptr, out int byteCnt, bool nullTerm = true, CharSet charSet = CharSet.Auto, long allocatedBytes = long.MaxValue) { if (value is null) { byteCnt = 0; return; } if (ptr == IntPtr.Zero) throw new ArgumentNullException(nameof(ptr)); var bytes = GetBytes(value, nullTerm, charSet); if (bytes.Length > allocatedBytes) throw new ArgumentOutOfRangeException(nameof(allocatedBytes)); byteCnt = bytes.Length; Marshal.Copy(bytes, 0, ptr, byteCnt); } private static bool IsValue(IntPtr ptr) => ptr.ToInt64() >> 16 == 0; } }