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