Vanara/Core/InteropServices/SafeMemString.cs

493 lines
24 KiB
C#

#nullable enable
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Security;
namespace Vanara.InteropServices;
/// <summary>Base abstract class for a string handler based on <see cref="SafeMemoryHandle{TMem}"/>.</summary>
/// <typeparam name="TMem">The type of the memory.</typeparam>
/// <seealso cref="SafeMemoryHandle{TMem}"/>
public abstract class SafeMemString<TMem> : SafeMemoryHandle<TMem>, IConvertible, IComparable<SafeMemString<TMem>>, IComparable, IComparable<string>, IEnumerable, IEnumerable<char>, IEquatable<SafeMemString<TMem>>, IEquatable<string> where TMem : IMemoryMethods, new()
{
/// <summary>The system default character set for evaluating CharSet.Auto.</summary>
protected static readonly CharSet defaultCharSet = Marshal.SystemDefaultCharSize == 2 ? CharSet.Unicode : CharSet.Ansi;
/// <summary>Initializes a new instance of the <see cref="SafeMemString{TMem}"/> class.</summary>
/// <param name="s">The string value.</param>
/// <param name="charSet">The character set.</param>
protected SafeMemString(string? s, CharSet charSet = CharSet.Unicode) : this(s, s is null ? 0 : s.Length + 1, charSet)
{
}
/// <summary>Initializes a new instance of the <see cref="SafeMemString{TMem}"/> class.</summary>
/// <param name="s">The string value.</param>
/// <param name="capacity">The capacity of the buffer, in characters.</param>
/// <param name="charSet">The character set.</param>
protected SafeMemString(string? s, int capacity, CharSet charSet = CharSet.Unicode) : this(capacity, charSet) => StringHelper.Write(s, handle, out _, true, charSet, Size);
/// <summary>Initializes a new instance of the <see cref="SafeMemString{TMem}"/> class.</summary>
/// <param name="s">The string value.</param>
/// <param name="charSet">The character set.</param>
protected SafeMemString(SecureString s, CharSet charSet = CharSet.Unicode) : this(IntPtr.Zero, charSet)
{
SetHandle(StringHelper.AllocSecureString(s, charSet, mm.AllocMem, out var sz));
base.sz = sz;
}
/// <summary>Initializes a new instance of the <see cref="SafeMemString{TMem}"/> class.</summary>
/// <param name="charLen">The size of the buffer in characters, including the null character terminator.</param>
/// <param name="charSet">The character set.</param>
protected SafeMemString(int charLen, CharSet charSet = CharSet.Unicode) : base(charLen * StringHelper.GetCharSize(charSet)) => CharSet = charSet != CharSet.Auto ? charSet : defaultCharSet;
/// <summary>Prevents a default instance of the <see cref="SafeMemString{TMem}"/> class from being created.</summary>
[ExcludeFromCodeCoverage]
protected SafeMemString() : base(0) { }
/// <summary>Initializes a new instance of the <see cref="SafeMemString{TMem}"/> class.</summary>
/// <param name="ptr">The PTR.</param>
/// <param name="charSet">The character set.</param>
/// <param name="ownsHandle"><c>true</c> to reliably release the handle during finalization; <c>false</c> to prevent it.</param>
/// <param name="allocatedBytes">The number of bytes allocated to <paramref name="ptr"/>.</param>
[ExcludeFromCodeCoverage]
protected SafeMemString(IntPtr ptr, CharSet charSet = CharSet.Unicode, bool ownsHandle = true, PInvoke.SizeT allocatedBytes = default) : base(ptr, allocatedBytes, ownsHandle) => CharSet = charSet == CharSet.Auto ? charSet : defaultCharSet;
/// <summary>Gets the number of allocated characters or 0 if the size is unknown (for example if it is holding a <see cref="SecureString"/>.</summary>
/// <value>The number of allocated characters.</value>
public int Capacity
{
get => Size / StringHelper.GetCharSize(CharSet);
set => Size = value * StringHelper.GetCharSize(CharSet);
}
/// <summary>Gets the character set of the assigned string.</summary>
/// <value>The character set.</value>
public CharSet CharSet { get; private set; } = CharSet.Unicode;
/// <summary>Gets a value indicating whether this instance contains a <see langword="null"/> pointer.</summary>
/// <value><see langword="true"/> if this instance is null; otherwise, <see langword="false"/>.</value>
public bool IsNull => handle == IntPtr.Zero;
/// <summary>Gets the number of characters in the current <see cref="SafeMemString{TMem}"/> object.</summary>
public int Length => ToString()?.Length ?? 0;
/// <summary>Gets or sets the <see cref="char"/> at the specified index.</summary>
/// <value>The <see cref="char"/>.</value>
/// <param name="index">The index of the character in the in-memory string.</param>
/// <returns>The character.</returns>
/// <exception cref="IndexOutOfRangeException"></exception>
public char this[int index]
{
get
{
var cs = StringHelper.GetCharSize(CharSet);
return index * cs >= Capacity || index < 0
? throw new IndexOutOfRangeException()
: CharSet == CharSet.Ansi ? Encoding.UTF8.GetChars(GetBytes(index * cs, cs))[0] : Encoding.Unicode.GetChars(GetBytes(index * cs, cs))[0];
}
set
{
var cs = StringHelper.GetCharSize(CharSet);
if (index * cs >= Capacity || index < 0) throw new IndexOutOfRangeException();
var bytes = CharSet == CharSet.Ansi ? Encoding.UTF8.GetBytes(new[] { value }) : Encoding.Unicode.GetBytes(new[] { value });
handle.Write(bytes, index * cs, Size);
}
}
/// <summary>Performs an explicit conversion from <see cref="SafeMemString{TMem}"/> to <see cref="char"/>.</summary>
/// <param name="s">The <see cref="SafeMemString{TMem}"/> instance.</param>
/// <returns>The result of the conversion.</returns>
/// <exception cref="InvalidCastException">Cannot convert an ANSI string to a Char pointer.</exception>
public static unsafe explicit operator char*(SafeMemString<TMem> s) => s.CharSet == CharSet.Unicode ? (char*)(void*)s.handle : throw new InvalidCastException("Cannot convert an ANSI string to a Char pointer.");
/// <summary>Returns the value of the <see cref="SafeHandle.handle"/> field.</summary>
/// <param name="s">The <see cref="SafeMemString{TMem}"/> instance.</param>
/// <returns>
/// An <see cref="IntPtr"/> representing the value of the handle field. If the handle has been marked invalid with <see
/// cref="SafeHandle.SetHandleAsInvalid"/>, this method still returns the original handle value, which can be a stale value.
/// </returns>
public static implicit operator IntPtr(SafeMemString<TMem> s) => s.DangerousGetHandle();
#if ALLOWSPAN
/// <summary>Performs an implicit conversion from <see cref="SafeMemString{TMem}"/> to <see cref="ReadOnlySpan{T}"/>.</summary>
/// <param name="value">The value.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator ReadOnlySpan<char>(SafeMemString<TMem> value) => value.AsReadOnlySpan<char>(value.Length);
/// <summary>Performs an implicit conversion from <see cref="SafeMemString{TMem}"/> to <see cref="Span{T}"/>.</summary>
/// <param name="value">The value.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator Span<char>(SafeMemString<TMem> value) => value.AsSpan<char>(value.Length);
#endif
/// <summary>Returns the string value held by a <see cref="SafeMemString{TMem}"/>.</summary>
/// <param name="s">The <see cref="SafeMemString{TMem}"/> instance.</param>
/// <returns>A <see cref="string"/> value held by the <see cref="SafeMemString{TMem}"/> or <c>null</c> if the handle or value is invalid.</returns>
public static implicit operator string?(SafeMemString<TMem> s) => s.ToString();
/// <summary>Implements the operator !=.</summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(SafeMemString<TMem>? left, SafeMemString<TMem>? right) => !(left == right);
/// <summary>Implements the operator !=.</summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(SafeMemString<TMem>? left, string right) => !(left == right);
/// <summary>Implements the operator &lt;.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator <(SafeMemString<TMem>? left, SafeMemString<TMem>? right) => left is null || left.IsNull ? right is not null && !right.IsNull : left.CompareTo(right) < 0;
/// <summary>Implements the operator &lt;.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator <(SafeMemString<TMem>? left, string right) => left is null || left.IsNull ? right is not null : left.CompareTo(right) < 0;
/// <summary>Implements the operator &lt;=.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator <=(SafeMemString<TMem>? left, SafeMemString<TMem>? right) => left is null || left.IsNull || left.CompareTo(right) <= 0;
/// <summary>Implements the operator &lt;=.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator <=(SafeMemString<TMem>? left, string right) => left is null || left.IsNull || left.CompareTo(right) <= 0;
/// <summary>Implements the operator ==.</summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(SafeMemString<TMem>? left, SafeMemString<TMem>? right) => left is null || left.IsNull ? right is null || right.IsNull : left.Equals(right);
/// <summary>Implements the operator ==.</summary>
/// <param name="left">The left value.</param>
/// <param name="right">The right value.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(SafeMemString<TMem>? left, string right) => left is null || left.IsNull ? right is null : left.Equals(right);
/// <summary>Implements the operator &gt;.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator >(SafeMemString<TMem>? left, SafeMemString<TMem>? right) => left is not null && !left.IsNull && left.CompareTo(right) > 0;
/// <summary>Implements the operator &gt;.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator >(SafeMemString<TMem>? left, string right) => left is not null && !left.IsNull && left.CompareTo(right) > 0;
/// <summary>Implements the operator &gt;=.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator >=(SafeMemString<TMem>? left, SafeMemString<TMem>? right) => left is null || left.IsNull ? right is null || right.IsNull : left.CompareTo(right) >= 0;
/// <summary>Implements the operator &gt;=.</summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
public static bool operator >=(SafeMemString<TMem>? left, string right) => left is null || left.IsNull ? right is null : left.CompareTo(right) >= 0;
/// <summary>Removes all characters from the current instance.</summary>
public void Clear()
{ Capacity = 1; this[0] = '\0'; }
/// <summary>Compares the current object with another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <para>
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <description>Less than zero</description>
/// <description>This object is less than the <paramref name="other"/> parameter.</description>
/// </item>
/// <item>
/// <description>Zero</description>
/// <description>This object is equal to <paramref name="other"/>.</description>
/// </item>
/// <item>
/// <description>Greater than zero</description>
/// <description>This object is greater than <paramref name="other"/>.</description>
/// </item>
/// </list>
/// </returns>
public int CompareTo(SafeMemString<TMem>? other) => string.Compare(ToString(), other?.ToString());
/// <summary>Compares the current object with a <see cref="string"/>.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <para>
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <description>Less than zero</description>
/// <description>This object is less than the <paramref name="other"/> parameter.</description>
/// </item>
/// <item>
/// <description>Zero</description>
/// <description>This object is equal to <paramref name="other"/>.</description>
/// </item>
/// <item>
/// <description>Greater than zero</description>
/// <description>This object is greater than <paramref name="other"/>.</description>
/// </item>
/// </list>
/// </returns>
public int CompareTo(string? other) => string.Compare(ToString(), other);
/// <summary>
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance
/// precedes, follows, or occurs in the same position in the sort order as the other object.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <para>
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
/// </para>
/// <list type="table">
/// <listheader>
/// <term>Value</term>
/// <term>Meaning</term>
/// </listheader>
/// <item>
/// <description>Less than zero</description>
/// <description>This object is less than the <paramref name="other"/> parameter.</description>
/// </item>
/// <item>
/// <description>Zero</description>
/// <description>This object is equal to <paramref name="other"/>.</description>
/// </item>
/// <item>
/// <description>Greater than zero</description>
/// <description>This object is greater than <paramref name="other"/>.</description>
/// </item>
/// </list>
/// </returns>
public int CompareTo(object? other) => other switch
{
null => IsNull ? 0 : 1,
string s => CompareTo(s),
SafeMemString<TMem> v => CompareTo(v),
_ => throw new ArgumentException($"Argument must be a string or {nameof(SafeMemString<TMem>)}", nameof(other))
};
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
public bool Equals(SafeMemString<TMem>? other) => string.Equals(ToString(), other?.ToString());
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
public bool Equals(string? other) => string.Equals(ToString(), other);
/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => ReferenceEquals(this, obj) || obj switch
{
null => false,
SafeMemString<TMem> ms => Equals(ms),
string s => Equals(s),
SafeAllocatedMemoryHandle m => m == handle,
_ => false,
};
/// <summary>Retrieves an enumerator that can iterate through the individual characters in this string.</summary>
/// <returns>An <see cref="IEnumerator{T}"/> object that can be used to iterate through individual characters in this string.</returns>
public IEnumerator<char> GetEnumerator() => ToString()?.GetEnumerator() ?? System.Linq.Enumerable.Empty<char>().GetEnumerator();
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
public override int GetHashCode() => ToString()?.GetHashCode() ?? 0;
/// <summary>Assigns a new string to this memory.</summary>
/// <param name="value">
/// The string value. This value can be <see langword="null"/>, but its length cannot be greater than the current <see cref="Capacity"/>.
/// </param>
public virtual void Set(string? value)
{
if (value is null)
ReleaseHandle();
else
StringHelper.Write(value, handle, out _, true, CharSet, Size);
}
/// <summary>Returns the string value held by this instance.</summary>
/// <returns>A <see cref="string"/> value held by this instance or <c>null</c> if the handle is invalid.</returns>
public override string? ToString() => IsInvalid ? null : StringHelper.GetString(handle, CharSet, Size == 0 ? long.MaxValue : (long)Size);
/// <summary>
/// Converts the value of this instance to an equivalent <see cref="string"/> using the specified culture-specific formatting information.
/// </summary>
/// <param name="provider">An <see cref="IFormatProvider"/> interface implementation that supplies culture-specific formatting information.</param>
/// <returns>A <see cref="string"/> instance equivalent to the value of this instance.</returns>
public virtual string ToString(IFormatProvider? provider) => ToString()?.ToString(provider) ?? string.Empty;
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="IEnumerator"/> object that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc/>
TypeCode IConvertible.GetTypeCode() => TypeCode.String;
/// <inheritdoc/>
bool IConvertible.ToBoolean(IFormatProvider? provider) => Convert.ToBoolean(ToString(), provider);
/// <inheritdoc/>
byte IConvertible.ToByte(IFormatProvider? provider) => Convert.ToByte(ToString(), provider);
/// <inheritdoc/>
char IConvertible.ToChar(IFormatProvider? provider) => Convert.ToChar(ToString() ?? throw new ArgumentNullException(), provider);
/// <inheritdoc/>
DateTime IConvertible.ToDateTime(IFormatProvider? provider) => Convert.ToDateTime(ToString(), provider);
/// <inheritdoc/>
decimal IConvertible.ToDecimal(IFormatProvider? provider) => Convert.ToDecimal(ToString(), provider);
/// <inheritdoc/>
double IConvertible.ToDouble(IFormatProvider? provider) => Convert.ToDouble(ToString(), provider);
/// <inheritdoc/>
short IConvertible.ToInt16(IFormatProvider? provider) => Convert.ToInt16(ToString(), provider);
/// <inheritdoc/>
int IConvertible.ToInt32(IFormatProvider? provider) => Convert.ToInt32(ToString(), provider);
/// <inheritdoc/>
long IConvertible.ToInt64(IFormatProvider? provider) => Convert.ToInt64(ToString(), provider);
/// <inheritdoc/>
sbyte IConvertible.ToSByte(IFormatProvider? provider) => Convert.ToSByte(ToString() ?? "0", provider);
/// <inheritdoc/>
float IConvertible.ToSingle(IFormatProvider? provider) => Convert.ToSingle(ToString(), provider);
/// <inheritdoc/>
object IConvertible.ToType(Type conversionType, IFormatProvider? provider)
{
var str = ToString();
return str is not null ? ((IConvertible)str).ToType(conversionType, provider) : throw new ArgumentNullException();
}
/// <inheritdoc/>
ushort IConvertible.ToUInt16(IFormatProvider? provider) => Convert.ToUInt16(ToString(), provider);
/// <inheritdoc/>
uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(ToString(), provider);
/// <inheritdoc/>
ulong IConvertible.ToUInt64(IFormatProvider? provider) => Convert.ToUInt64(ToString(), provider);
/// <summary>Appends a copy of the specified string to this instance.</summary>
/// <param name="value">The string to append.</param>
/// <returns>A reference to this instance after the append operation has completed.</returns>
protected virtual void Append(string? value)
{
if (!string.IsNullOrEmpty(value))
{
var curLen = Length;
Capacity += value?.Length ?? 0;
StringHelper.Write(value, DangerousGetHandle().Offset(curLen * 2), out _, true, CharSet.Unicode, Size);
}
}
/// <summary>Inserts a string into this instance at the specified character position.</summary>
/// <param name="index">The position in this instance where insertion begins.</param>
/// <param name="value">The string to insert.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index"/> is less than zero or greater than the current length of this instance.
/// </exception>
protected virtual void Insert(int index, string? value)
{
if (index > Length || index < 0)
throw new ArgumentOutOfRangeException(nameof(index));
if (value is not null && value != string.Empty)
{
var curLen = Length;
Capacity += value.Length;
handle.Offset(index * 2).CopyTo(handle.Offset(curLen * 2), 2 * (curLen - index + 1));
StringHelper.Write(value, handle.Offset(index * 2), out _, false, CharSet.Unicode, Size);
}
}
/// <summary>Removes the specified range of characters from this instance.</summary>
/// <param name="startIndex">The zero-based position in this instance where removal begins.</param>
/// <param name="length">The number of characters to remove.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="startIndex"/> or <paramref name="length"/> is less than zero, or <paramref name="startIndex"/> + <paramref
/// name="length"/> is greater than the length of this instance.
/// </exception>
protected virtual void Remove(int startIndex, int length)
{
if (startIndex > Length || startIndex < 0 || startIndex + length > Length)
throw new ArgumentOutOfRangeException(nameof(startIndex));
handle.Offset((startIndex + length) * 2).CopyTo(handle.Offset(startIndex * 2), 2 * length);
if (length > 64)
Capacity = Length + 1;
}
/// <summary>Replaces the specified old value.</summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">The string that replaces <paramref name="oldValue"/>, or <see langword="null"/>.</param>
/// <param name="startIndex">The position in this instance where the substring begins.</param>
/// <param name="count">The length of the substring.</param>
protected virtual void Replace(string oldValue, string? newValue, int startIndex, int count)
{
if (IsNull || Length == 0) return;
var sb = new StringBuilder(ToString());
sb.Replace(oldValue, newValue, startIndex, count);
Capacity = sb.Length;
Set(sb.ToString());
}
/// <summary>Replaces, within a substring of this instance, all occurrences of a specified character with another specified character.</summary>
/// <param name="oldChar">The character to replace.</param>
/// <param name="newChar">The character that replaces <paramref name="oldChar"/>.</param>
/// <param name="startIndex">The position in this instance where the substring begins.</param>
/// <param name="count">The length of the substring.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <para><paramref name="startIndex"/> + <paramref name="count"/> is greater than the length of the value of this instance.</para>
/// <para>-or-</para>
/// <para><paramref name="startIndex"/> or <paramref name="count"/> is less than zero.</para>
/// </exception>
protected virtual void Replace(char oldChar, char newChar, int startIndex, int count)
{
var currentLength = Length;
if ((uint)startIndex > (uint)currentLength)
throw new ArgumentOutOfRangeException(nameof(startIndex));
if (count < 0 || startIndex > currentLength - count)
throw new ArgumentOutOfRangeException(nameof(count));
var endIndex = startIndex + count;
var chunk = (Span<char>)this;
for (var i = startIndex; i < endIndex; i++)
{
if (chunk[i] == oldChar)
chunk[i] = newChar;
}
}
}