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