Vanara/Core/Extensions/StringHelper.cs

260 lines
14 KiB
C#

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using Vanara.InteropServices;
namespace Vanara.Extensions
{
/// <summary>A safe class that represents an object that is pinned in memory.</summary>
/// <seealso cref="System.IDisposable"/>
public static class StringHelper
{
/// <summary>Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters.</summary>
/// <param name="count">The number of characters, inclusive of the null terminator.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The address of the block of memory allocated.</returns>
public static IntPtr AllocChars(uint count, Func<int, IntPtr> 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;
}
/// <summary>Allocates a block of memory allocated from the unmanaged COM task allocator sufficient to hold the number of specified characters.</summary>
/// <param name="count">The number of characters, inclusive of the null terminator.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The address of the block of memory allocated.</returns>
public static IntPtr AllocChars(uint count, CharSet charSet = CharSet.Auto) => AllocChars(count, Marshal.AllocCoTaskMem, charSet);
/// <summary>Copies the contents of a managed <see cref="SecureString"/> object to a block of memory allocated from the unmanaged COM task allocator.</summary>
/// <param name="s">The managed object to copy.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The address, in unmanaged memory, where the <paramref name="s"/> parameter was copied to, or 0 if a null object was supplied.</returns>
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);
}
/// <summary>Copies the contents of a managed <see cref="SecureString"/> object to a block of memory allocated from a supplied allocation method.</summary>
/// <param name="s">The managed object to copy.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <returns>The address, in unmanaged memory, where the <paramref name="s"/> parameter was copied to, or 0 if a null object was supplied.</returns>
public static IntPtr AllocSecureString(SecureString s, CharSet charSet, Func<int, IntPtr> memAllocator) => AllocSecureString(s, charSet, memAllocator, out _);
/// <summary>Copies the contents of a managed <see cref="SecureString"/> object to a block of memory allocated from a supplied allocation method.</summary>
/// <param name="s">The managed object to copy.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <param name="allocatedBytes">Returns the number of allocated bytes for the string.</param>
/// <returns>The address, in unmanaged memory, where the <paramref name="s"/> parameter was copied to, or 0 if a null object was supplied.</returns>
public static IntPtr AllocSecureString(SecureString s, CharSet charSet, Func<int, IntPtr> 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;
}
/// <summary>Copies the contents of a managed String to a block of memory allocated from the unmanaged COM task allocator.</summary>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The allocated memory block, or 0 if <paramref name="s"/> is null.</returns>
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));
/// <summary>Copies the contents of a managed String to a block of memory allocated from a supplied allocation method.</summary>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <returns>The allocated memory block, or 0 if <paramref name="s"/> is null.</returns>
public static IntPtr AllocString(string s, CharSet charSet, Func<int, IntPtr> memAllocator) => AllocString(s, charSet, memAllocator, out _);
/// <summary>
/// Copies the contents of a managed String to a block of memory allocated from a supplied allocation method.
/// </summary>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set.</param>
/// <param name="memAllocator">The method used to allocate the memory.</param>
/// <param name="allocatedBytes">Returns the number of allocated bytes for the string.</param>
/// <returns>The allocated memory block, or 0 if <paramref name="s" /> is null.</returns>
public static IntPtr AllocString(string s, CharSet charSet, Func<int, IntPtr> 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;
}
/// <summary>
/// Zeros out the allocated memory behind a secure string and then frees that memory.
/// </summary>
/// <param name="ptr">The address of the memory to be freed.</param>
/// <param name="sizeInBytes">The size in bytes of the memory pointed to by <paramref name="ptr"/>.</param>
/// <param name="memFreer">The memory freer.</param>
public static void FreeSecureString(IntPtr ptr, int sizeInBytes, Action<IntPtr> memFreer)
{
if (IsValue(ptr)) return;
var b = new byte[sizeInBytes];
Marshal.Copy(b, 0, ptr, b.Length);
memFreer(ptr);
}
/// <summary>Frees a block of memory allocated by the unmanaged COM task memory allocator for a string.</summary>
/// <param name="ptr">The address of the memory to be freed.</param>
/// <param name="charSet">The character set of the string.</param>
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);
}
/// <summary>Gets the encoded bytes for a string including an optional null terminator.</summary>
/// <param name="value">The string value to convert.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the resulting byte array.</param>
/// <param name="charSet">The character set.</param>
/// <returns>A byte array including <paramref name="value"/> encoded as per <paramref name="charSet"/> and the optional null terminator.</returns>
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;
}
/// <summary>Gets the number of bytes required to store the string.</summary>
/// <param name="value">The string value.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the count if <paramref name="value"/> does not equal <c>null</c>.</param>
/// <param name="charSet">The character set.</param>
/// <returns>The number of bytes required to store <paramref name="value"/>. Returns 0 if <paramref name="value"/> is <c>null</c>.</returns>
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);
}
/// <summary>Gets the size of a character defined by the supplied <see cref="CharSet"/>.</summary>
/// <param name="charSet">The character set to size.</param>
/// <returns>The size of a standard character, in bytes, from <paramref name="charSet"/>.</returns>
public static int GetCharSize(CharSet charSet = CharSet.Auto) => charSet == CharSet.Auto ? Marshal.SystemDefaultCharSize : (charSet == CharSet.Unicode ? 2 : 1);
/// <summary>
/// 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.
/// </summary>
/// <param name="ptr">The address of the first character.</param>
/// <param name="charSet">The character set of the string.</param>
/// <param name="allocatedBytes">If known, the total number of bytes allocated to the native memory in <paramref name="ptr"/>.</param>
/// <returns>
/// A managed string that holds a copy of the unmanaged string if the value of the <paramref name="ptr"/> parameter is not null;
/// otherwise, this method returns null.
/// </returns>
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();
}
/// <summary>
/// Allocates a managed String and copies all characters up to the first null character or at most <paramref name="length"/> characters from a string stored in unmanaged memory into it.
/// </summary>
/// <param name="ptr">The address of the first character.</param>
/// <param name="length">The number of characters to copy.</param>
/// <param name="charSet">The character set of the string.</param>
/// <returns>
/// A managed string that holds a copy of the unmanaged string if the value of the <paramref name="ptr"/> parameter is not null;
/// otherwise, this method returns null.
/// </returns>
public static string GetString(IntPtr ptr, int length, CharSet charSet = CharSet.Auto) => GetString(ptr, charSet, length * GetCharSize(charSet));
/// <summary>Indicates whether a specified string is <see langword="null"/>, empty, or consists only of white-space characters.</summary>
/// <param name="value">The string to test.</param>
/// <returns>
/// <see langword="true"/> if the <paramref name="value"/> parameter is <see langword="null"/> or <see cref="string.Empty"/>, or if
/// value consists exclusively of white-space characters.
/// </returns>
public static bool IsNullOrWhiteSpace(string value) => value is null || value.All(c => char.IsWhiteSpace(c));
/// <summary>Refreshes the memory block from the unmanaged COM task allocator and copies the contents of a new managed String.</summary>
/// <param name="ptr">The address of the first character.</param>
/// <param name="charLen">Receives the new character length of the allocated memory block.</param>
/// <param name="s">A managed string to be copied.</param>
/// <param name="charSet">The character set of the string.</param>
/// <returns><c>true</c> if the memory block was reallocated; <c>false</c> if set to null.</returns>
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;
}
/// <summary>Writes the specified string to a pointer to allocated memory.</summary>
/// <param name="value">The string value.</param>
/// <param name="ptr">The pointer to the allocated memory.</param>
/// <param name="byteCnt">The resulting number of bytes written.</param>
/// <param name="nullTerm">if set to <c>true</c> include a null terminator at the end of the string in the count if <paramref name="value"/> does not equal <c>null</c>.</param>
/// <param name="charSet">The character set of the string.</param>
/// <param name="allocatedBytes">If known, the total number of bytes allocated to the native memory in <paramref name="ptr"/>.</param>
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;
}
}