From 7ce7b96209d9fd9f014f9849232bcd7ef0d1cfa3 Mon Sep 17 00:00:00 2001 From: David Hall Date: Tue, 25 Jun 2019 17:10:57 -0600 Subject: [PATCH] * Derived SafeCoTaskMemString from base class SafeMemString derived from SafeMemHandle. * BREAKING CHANGE: Removed CharCapacity and made Capacity show char allocation. Size (from parent) shows byte capacity. Mimics StringBuilder. --- Core/Extensions/StringHelper.cs | 25 +++++- Core/InteropServices/SafeCoTaskMemString.cs | 71 ++++------------- Core/InteropServices/SafeMemString.cs | 89 ++++++++++++++++++++++ .../CredentialsDialog/AuthenticationBuffer.cs | 6 +- Windows.Shell/ShellObjects/ShellItem.cs | 4 +- 5 files changed, 129 insertions(+), 66 deletions(-) create mode 100644 Core/InteropServices/SafeMemString.cs diff --git a/Core/Extensions/StringHelper.cs b/Core/Extensions/StringHelper.cs index 9b754c7e..545bfeeb 100644 --- a/Core/Extensions/StringHelper.cs +++ b/Core/Extensions/StringHelper.cs @@ -11,13 +11,14 @@ namespace Vanara.Extensions { /// 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, CharSet charSet = CharSet.Auto) + public static IntPtr AllocChars(uint count, Func memAllocator, CharSet charSet = CharSet.Auto) { if (count == 0) return IntPtr.Zero; var sz = GetCharSize(charSet); - var ptr = Marshal.AllocCoTaskMem((int)count * sz); + var ptr = memAllocator((int)count * sz); if (count > 0) { if (sz == 1) @@ -28,6 +29,12 @@ namespace Vanara.Extensions 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. @@ -45,10 +52,19 @@ namespace Vanara.Extensions /// 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) + 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 = StringHelper.GetCharSize(charSet); + 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); @@ -57,6 +73,7 @@ namespace Vanara.Extensions var b = encoding.GetBytes(str); var p = memAllocator(b.Length); Marshal.Copy(b, 0, p, b.Length); + allocatedBytes = b.Length; return p; } diff --git a/Core/InteropServices/SafeCoTaskMemString.cs b/Core/InteropServices/SafeCoTaskMemString.cs index 15809a86..60ff97fb 100644 --- a/Core/InteropServices/SafeCoTaskMemString.cs +++ b/Core/InteropServices/SafeCoTaskMemString.cs @@ -1,96 +1,53 @@ -using Microsoft.Win32.SafeHandles; -using System; +using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Security; -using Vanara.Extensions; namespace Vanara.InteropServices { /// Safely handles an unmanaged memory allocated Unicode string. - /// - public class SafeCoTaskMemString : SafeHandleZeroOrMinusOneIsInvalid + public class SafeCoTaskMemString : SafeMemString { - private readonly CharSet chSet = CharSet.Unicode; - /// Initializes a new instance of the class. /// The string value. /// The character set. - /// true to reliably release the handle during finalization; false to prevent it. - public SafeCoTaskMemString(string s, CharSet charSet = CharSet.Unicode, bool ownsHandle = true) : this(StringHelper.AllocString(s, charSet), charSet, ownsHandle) + public SafeCoTaskMemString(string s, CharSet charSet = CharSet.Unicode) : base(s, charSet) + { + } + + /// Initializes a new instance of the class. + /// The string value. + /// The size of the buffer in characters. + /// The character set. + public SafeCoTaskMemString(string s, int capacity, CharSet charSet = CharSet.Unicode) : base(s, capacity, charSet) { - Capacity = StringHelper.GetByteCount(s, true, charSet); } /// Initializes a new instance of the class. /// The string value. /// The character set. - public SafeCoTaskMemString(SecureString s, CharSet charSet = CharSet.Unicode) : this(StringHelper.AllocSecureString(s, charSet), charSet) + public SafeCoTaskMemString(SecureString s, CharSet charSet = CharSet.Unicode) : base(s, charSet) { - Capacity = s == null ? 0 : StringHelper.GetCharSize(charSet) * (s.Length + 1); } /// Initializes a new instance of the class. /// The size of the buffer in characters, including the null character terminator. /// The character set. - public SafeCoTaskMemString(int charLen, CharSet charSet = CharSet.Unicode) : this(StringHelper.AllocChars((uint)charLen, charSet), charSet) + public SafeCoTaskMemString(int charLen, CharSet charSet = CharSet.Unicode) : base(charLen, charSet) { - Capacity = charLen * StringHelper.GetCharSize(charSet); } - /// Prevents a default instance of the class from being created. - [ExcludeFromCodeCoverage] - private SafeCoTaskMemString() : base(true) { } - /// Initializes a new instance of the class. /// The PTR. /// The character set. /// true to reliably release the handle during finalization; false to prevent it. [ExcludeFromCodeCoverage] - private SafeCoTaskMemString(IntPtr ptr, CharSet charSet = CharSet.Unicode, bool ownsHandle = true) : base(ownsHandle) + private SafeCoTaskMemString(IntPtr ptr, CharSet charSet = CharSet.Unicode, bool ownsHandle = true) : base(ptr, charSet, ownsHandle) { - chSet = charSet; - SetHandle(ptr); } /// Represents a null value. Used primarily for comparrison. /// A null value. public static SafeCoTaskMemString Null => new SafeCoTaskMemString(IntPtr.Zero, CharSet.Unicode, false); - - /// Gets the number of allocated bytes or -1 if the size is unknown (for example if it is holding a . - /// The number of allocated bytes. - public int Capacity { get; } = -1; - - /// Gets the number of allocated characters or -1 if the size is unknown (for example if it is holding a . - /// The number of characters bytes. - public int CharCapacity => Capacity == -1 ? -1 : Capacity / StringHelper.GetCharSize(chSet); - - /// 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 explicit operator IntPtr(SafeCoTaskMemString s) => s.DangerousGetHandle(); - - /// 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(SafeCoTaskMemString s) => s?.ToString(); - - /// 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, chSet); - - /// When overridden in a derived class, executes the code required to free the handle. - /// - /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a - /// releaseHandleFailed MDA Managed Debugging Assistant. - /// - protected override bool ReleaseHandle() - { - StringHelper.FreeString(handle, chSet); - return true; - } } } \ No newline at end of file diff --git a/Core/InteropServices/SafeMemString.cs b/Core/InteropServices/SafeMemString.cs new file mode 100644 index 00000000..23b2ffaa --- /dev/null +++ b/Core/InteropServices/SafeMemString.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security; +using Vanara.Extensions; + +namespace Vanara.InteropServices +{ + /// Base abstract class for a string handler based on . + /// The type of the memory. + /// + public abstract class SafeMemString : SafeMemoryHandle where TMem : IMemoryMethods, new() + { + private readonly CharSet chSet = CharSet.Unicode; + + /// 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)) + { + chSet = charSet; + } + + /// 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. + [ExcludeFromCodeCoverage] + protected SafeMemString(IntPtr ptr, CharSet charSet = CharSet.Unicode, bool ownsHandle = true) : base(ptr, 0, ownsHandle) + { + chSet = charSet; + } + + /// 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 => Size / StringHelper.GetCharSize(chSet); + + /// Gets the number of characters in the current object. + public int Length => ToString().Length; + + /// 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 explicit operator IntPtr(SafeMemString s) => s.DangerousGetHandle(); + + /// 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(); + + /// 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, chSet, Size); + } +} \ No newline at end of file diff --git a/WIndows.Forms/Dialogs/CredentialsDialog/AuthenticationBuffer.cs b/WIndows.Forms/Dialogs/CredentialsDialog/AuthenticationBuffer.cs index 04cd6027..979a1b01 100644 --- a/WIndows.Forms/Dialogs/CredentialsDialog/AuthenticationBuffer.cs +++ b/WIndows.Forms/Dialogs/CredentialsDialog/AuthenticationBuffer.cs @@ -69,9 +69,9 @@ namespace Vanara.Security var pUserName = new SafeCoTaskMemString(CRED_MAX_USERNAME_LENGTH); var pDomainName = new SafeCoTaskMemString(CRED_MAX_USERNAME_LENGTH); var pPassword = new SafeCoTaskMemString(CREDUI_MAX_PASSWORD_LENGTH); - var userNameSize = pUserName.CharCapacity; - var domainNameSize = pDomainName.CharCapacity; - var passwordSize = pPassword.CharCapacity; + var userNameSize = pUserName.Capacity; + var domainNameSize = pDomainName.Capacity; + var passwordSize = pPassword.Capacity; if (!CredUnPackAuthenticationBuffer(decryptProtectedCredentials ? CredPackFlags.CRED_PACK_PROTECTED_CREDENTIALS : 0x0, DangerousHandle, Size, (IntPtr)pUserName, ref userNameSize, (IntPtr)pDomainName, ref domainNameSize, (IntPtr)pPassword, ref passwordSize)) diff --git a/Windows.Shell/ShellObjects/ShellItem.cs b/Windows.Shell/ShellObjects/ShellItem.cs index 9a5fbe96..6cf78579 100644 --- a/Windows.Shell/ShellObjects/ShellItem.cs +++ b/Windows.Shell/ShellObjects/ShellItem.cs @@ -849,12 +849,12 @@ namespace Vanara.Windows.Shell var result = new StringBuilder(512); if (!SHGetPathFromIDList(PIDL, result)) throw new ArgumentException(); - return new SafeCoTaskMemString(result.ToString(), CharSet.Unicode, false); + return new SafeCoTaskMemString(result.ToString(), CharSet.Unicode); } var parentFolder = InternalGetParent().GetIShellFolder(); var child = PIDL.LastId; - return new SafeCoTaskMemString(parentFolder.GetDisplayNameOf(child, (SHGDNF)((int)sigdnName & 0xffff)), CharSet.Unicode, false); + return new SafeCoTaskMemString(parentFolder.GetDisplayNameOf(child, (SHGDNF)((int)sigdnName & 0xffff)), CharSet.Unicode); } public IShellItem GetParent()