diff --git a/Core/Extensions/StringHelper.cs b/Core/Extensions/StringHelper.cs index d3962c80..b30acf1e 100644 --- a/Core/Extensions/StringHelper.cs +++ b/Core/Extensions/StringHelper.cs @@ -70,12 +70,22 @@ namespace Vanara.Extensions /// 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) + 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) return IntPtr.Zero; + if (s == null) { allocatedBytes = 0; return IntPtr.Zero; } var b = s.GetBytes(true, charSet); var p = memAllocator(b.Length); - Marshal.Copy(b, 0, p, b.Length); + Marshal.Copy(b, 0, p, allocatedBytes = b.Length); return p; } @@ -139,11 +149,36 @@ namespace Vanara.Extensions /// 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 from a string stored in unmanaged memory into it. + /// + /// 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. - /// 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) => IsValue(ptr) ? null : (charSet == CharSet.Auto ? Marshal.PtrToStringAuto(ptr) : (charSet == CharSet.Unicode ? Marshal.PtrToStringUni(ptr) : Marshal.PtrToStringAnsi(ptr))); + /// 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 < 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. @@ -155,8 +190,7 @@ namespace Vanara.Extensions /// 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) => - IsValue(ptr) ? null : (charSet == CharSet.Auto ? Marshal.PtrToStringAuto(ptr, length) : (charSet == CharSet.Unicode ? Marshal.PtrToStringUni(ptr, length) : Marshal.PtrToStringAnsi(ptr, length))); + public static string GetString(IntPtr ptr, int length, CharSet charSet = CharSet.Auto) => GetString(ptr, charSet, length * GetCharSize(charSet)); /// 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. diff --git a/UnitTests/Core/Extensions/StringHelperTests.cs b/UnitTests/Core/Extensions/StringHelperTests.cs index 9d5b7d3c..bbc6b22f 100644 --- a/UnitTests/Core/Extensions/StringHelperTests.cs +++ b/UnitTests/Core/Extensions/StringHelperTests.cs @@ -87,5 +87,28 @@ namespace Vanara.Extensions.Tests { Assert.That(StringHelper.GetByteCount(value, nullTerm, cs), Is.EqualTo(ret)); } + + [TestCase("BOO", CharSet.Ansi)] + [TestCase("BOO", CharSet.Unicode)] + [TestCase("", CharSet.Ansi)] + [TestCase("", CharSet.Unicode)] + [TestCase(null, CharSet.Ansi)] + [TestCase(null, CharSet.Unicode)] + public void GetStringTest1(string value, CharSet cs) + { + IntPtr ptr = default; + try + { + ptr = StringHelper.AllocString(value, cs, Marshal.AllocCoTaskMem, out var count); + Assert.That(count, Is.EqualTo((value?.Length + 1) * StringHelper.GetCharSize(cs) ?? 0)); + Assert.That(StringHelper.GetString(ptr, cs), Is.EqualTo(value)); + Assert.That(StringHelper.GetString(ptr, cs, count * 2), Is.EqualTo(value)); + Assert.That(StringHelper.GetString(ptr, cs, count / 2), Is.EqualTo(string.IsNullOrEmpty(value) ? value : value.Substring(0, (value.Length + 1) / 2))); + } + finally + { + Marshal.FreeCoTaskMem(ptr); + } + } } } \ No newline at end of file