Updated SafeMoveableHGlobalHandle class and added tests.

pull/363/head
David Hall 2023-01-04 17:08:36 -07:00
parent 7c0c7fe66c
commit 63203f558e
3 changed files with 242 additions and 175 deletions

View File

@ -5,91 +5,95 @@ using System.Runtime.InteropServices;
using Vanara.Extensions;
using Vanara.InteropServices;
namespace Vanara.PInvoke
namespace Vanara.PInvoke;
public static partial class Kernel32
{
public static partial class Kernel32
/// <summary>A <see cref="SafeHandle"/> for memory allocated as moveable HGLOBAL.</summary>
/// <seealso cref="SafeHandle"/>
public class SafeMoveableHGlobalHandle : SafeMemoryHandleExt<MoveableHGlobalMemoryMethods>
{
/// <summary>A <see cref="SafeHandle"/> for memory allocated as moveable HGLOBAL.</summary>
/// <seealso cref="SafeHandle"/>
public class SafeMoveableHGlobalHandle : SafeMemoryHandleExt<MoveableHGlobalMemoryMethods>
/// <summary>Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class.</summary>
/// <param name="handle">The handle.</param>
/// <param name="ownsHandle">if set to <c>true</c> if this class is responsible for freeing the memory on disposal.</param>
public SafeMoveableHGlobalHandle(IntPtr handle, bool ownsHandle = true) : base(handle, handle == default ? 0 : GlobalSize(handle), ownsHandle) { }
/// <summary>Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class.</summary>
/// <param name="size">The size of memory to allocate, in bytes.</param>
/// <exception cref="System.ArgumentOutOfRangeException">size - The value of this argument must be non-negative</exception>
public SafeMoveableHGlobalHandle(SizeT size) : base(size) { }
/// <summary>
/// Allocates from unmanaged memory to represent an array of pointers and marshals the unmanaged pointers (IntPtr) to the native
/// array equivalent.
/// </summary>
/// <param name="bytes">Array of unmanaged pointers</param>
/// <returns>SafeMoveableHGlobalHandle object to an native (unmanaged) array of pointers</returns>
public SafeMoveableHGlobalHandle(byte[] bytes) : base(bytes?.Length ?? 0)
{
/// <summary>Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class.</summary>
/// <param name="handle">The handle.</param>
/// <param name="ownsHandle">if set to <c>true</c> if this class is responsible for freeing the memory on disposal.</param>
public SafeMoveableHGlobalHandle(IntPtr handle, bool ownsHandle = true) : base(handle, handle == default ? 0 : GlobalSize(handle), ownsHandle) { }
/// <summary>Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class.</summary>
/// <param name="size">The size of memory to allocate, in bytes.</param>
/// <exception cref="System.ArgumentOutOfRangeException">size - The value of this argument must be non-negative</exception>
public SafeMoveableHGlobalHandle(SizeT size) : base(size) { }
/// <summary>
/// Allocates from unmanaged memory to represent an array of pointers and marshals the unmanaged pointers (IntPtr) to the native
/// array equivalent.
/// </summary>
/// <param name="bytes">Array of unmanaged pointers</param>
/// <returns>SafeMoveableHGlobalHandle object to an native (unmanaged) array of pointers</returns>
public SafeMoveableHGlobalHandle(byte[] bytes) : base(bytes?.Length ?? 0)
{
if (Size == 0) return;
CallLocked(p => Marshal.Copy(bytes, 0, p, sz));
}
/// <summary>
/// Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class from a <see cref="SafeAllocatedMemoryHandle"/>
/// instance, copying all the memory.
/// </summary>
/// <param name="source">The source memory block.</param>
public SafeMoveableHGlobalHandle(SafeAllocatedMemoryHandle source) : base(source) { }
/// <summary>Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class.</summary>
[ExcludeFromCodeCoverage]
internal SafeMoveableHGlobalHandle() : base(0) { }
/// <summary>Represents a NULL memory pointer.</summary>
public static SafeMoveableHGlobalHandle Null { get; } = new SafeMoveableHGlobalHandle(IntPtr.Zero, false);
/// <summary>
/// Allocates from unmanaged memory to represent a structure with a variable length array at the end and marshal these structure
/// elements. It is the callers responsibility to marshal what precedes the trailing array into the unmanaged memory. ONLY
/// structures with attribute StructLayout of LayoutKind.Sequential are supported.
/// </summary>
/// <typeparam name="T">Type of the trailing array of structures</typeparam>
/// <param name="values">Collection of structure objects</param>
/// <param name="prefixBytes">Number of bytes preceding the trailing array of structures</param>
/// <returns><see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) structure with a trail array of structures</returns>
public static SafeMoveableHGlobalHandle CreateFromList<T>(IEnumerable<T> values, int prefixBytes = 0) =>
new(InteropExtensions.MarshalToPtr(values, mm.AllocMem, out _, prefixBytes, mm.LockMem, mm.UnlockMem), true);
/// <summary>Allocates from unmanaged memory sufficient memory to hold an array of strings.</summary>
/// <param name="values">The list of strings.</param>
/// <param name="packing">The packing type for the strings.</param>
/// <param name="charSet">The character set to use for the strings.</param>
/// <param name="prefixBytes">Number of bytes preceding the trailing strings.</param>
/// <returns>
/// <see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) array of strings stored using the <paramref
/// name="packing"/> model and the character set defined by <paramref name="charSet"/>.
/// </returns>
public static SafeMoveableHGlobalHandle CreateFromStringList(IEnumerable<string> values, StringListPackMethod packing = StringListPackMethod.Concatenated,
CharSet charSet = CharSet.Auto, int prefixBytes = 0) => new(InteropExtensions.MarshalToPtr(values, packing, mm.AllocMem, out _, charSet, prefixBytes, mm.LockMem, mm.UnlockMem), true);
/// <summary>Allocates from unmanaged memory sufficient memory to hold an object of type T.</summary>
/// <typeparam name="T">Native type</typeparam>
/// <param name="value">The value.</param>
/// <returns><see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) memory block the size of T.</returns>
public static SafeMoveableHGlobalHandle CreateFromStructure<T>(in T value = default) =>
new(InteropExtensions.MarshalToPtr(value, mm.AllocMem, out _, 0, mm.LockMem, mm.UnlockMem), true);
/// <summary>Converts an <see cref="IntPtr"/> to a <see cref="SafeMoveableHGlobalHandle"/> where it owns the reference.</summary>
/// <param name="ptr">The <see cref="IntPtr"/>.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator SafeMoveableHGlobalHandle(IntPtr ptr) => new(ptr, true);
/// <summary>Runs a delegate method while locking the memory.</summary>
/// <typeparam name="T">The output type.</typeparam>
/// <param name="action">The action to perform while memory is locked.</param>
/// <returns>The output from the action.</returns>
public new T CallLocked<T>(Func<IntPtr, T> action) => base.CallLocked(action);
if (Size == 0) return;
CallLocked(p => Marshal.Copy(bytes, 0, p, sz));
}
/// <summary>
/// Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class from a <see cref="SafeAllocatedMemoryHandle"/>
/// instance, copying all the memory.
/// </summary>
/// <param name="source">The source memory block.</param>
public SafeMoveableHGlobalHandle(SafeAllocatedMemoryHandle source) : base(source) { }
/// <summary>Initializes a new instance of the <see cref="SafeMoveableHGlobalHandle"/> class.</summary>
[ExcludeFromCodeCoverage]
internal SafeMoveableHGlobalHandle() : base(0) { }
/// <summary>Represents a NULL memory pointer.</summary>
public static SafeMoveableHGlobalHandle Null { get; } = new SafeMoveableHGlobalHandle(IntPtr.Zero, false);
/// <summary>
/// Allocates from unmanaged memory to represent a structure with a variable length array at the end and marshal these structure
/// elements. It is the callers responsibility to marshal what precedes the trailing array into the unmanaged memory. ONLY
/// structures with attribute StructLayout of LayoutKind.Sequential are supported.
/// </summary>
/// <typeparam name="T">Type of the trailing array of structures</typeparam>
/// <param name="values">Collection of structure objects</param>
/// <param name="prefixBytes">Number of bytes preceding the trailing array of structures</param>
/// <returns><see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) structure with a trail array of structures</returns>
public static SafeMoveableHGlobalHandle CreateFromList<T>(IEnumerable<T> values, int prefixBytes = 0) =>
new(InteropExtensions.MarshalToPtr(values, mm.AllocMem, out _, prefixBytes, mm.LockMem, mm.UnlockMem), true);
/// <summary>Allocates from unmanaged memory sufficient memory to hold an array of strings.</summary>
/// <param name="values">The list of strings.</param>
/// <param name="packing">The packing type for the strings.</param>
/// <param name="charSet">The character set to use for the strings.</param>
/// <param name="prefixBytes">Number of bytes preceding the trailing strings.</param>
/// <returns>
/// <see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) array of strings stored using the <paramref
/// name="packing"/> model and the character set defined by <paramref name="charSet"/>.
/// </returns>
public static SafeMoveableHGlobalHandle CreateFromStringList(IEnumerable<string> values, StringListPackMethod packing = StringListPackMethod.Concatenated,
CharSet charSet = CharSet.Auto, int prefixBytes = 0) => new(InteropExtensions.MarshalToPtr(values, packing, mm.AllocMem, out _, charSet, prefixBytes, mm.LockMem, mm.UnlockMem), true);
/// <summary>Allocates from unmanaged memory sufficient memory to hold an object of type T.</summary>
/// <typeparam name="T">Native type</typeparam>
/// <param name="value">The value.</param>
/// <returns><see cref="SafeMoveableHGlobalHandle"/> object to an native (unmanaged) memory block the size of T.</returns>
public static SafeMoveableHGlobalHandle CreateFromStructure<T>(in T value = default) =>
new(InteropExtensions.MarshalToPtr(value, mm.AllocMem, out _, 0, mm.LockMem, mm.UnlockMem), true);
/// <summary>Converts an <see cref="SafeMoveableHGlobalHandle"/> to a <see cref="HGLOBAL"/> where it owns the reference.</summary>
/// <param name="ptr">The <see cref="SafeMoveableHGlobalHandle"/>.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator HGLOBAL(SafeMoveableHGlobalHandle ptr) => ptr.handle;
/// <summary>Converts an <see cref="IntPtr"/> to a <see cref="SafeMoveableHGlobalHandle"/> where it owns the reference.</summary>
/// <param name="ptr">The <see cref="IntPtr"/>.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator SafeMoveableHGlobalHandle(IntPtr ptr) => new(ptr, true);
/// <summary>Runs a delegate method while locking the memory.</summary>
/// <typeparam name="T">The output type.</typeparam>
/// <param name="action">The action to perform while memory is locked.</param>
/// <returns>The output from the action.</returns>
public new T CallLocked<T>(Func<IntPtr, T> action) => base.CallLocked(action);
}
}

View File

@ -44,13 +44,8 @@ namespace Vanara.PInvoke
GMEM_NOT_BANKED = 0x1000,
/// <summary>Allocate sharable memory.</summary>
[Obsolete("Value is obsolete, but is provided for compatibility with 16-bit Windows.")]
GMEM_SHARE = 0x2000,
/// <summary>Allocate sharable memory.</summary>
[Obsolete("Value is obsolete, but is provided for compatibility with 16-bit Windows.")]
GMEM_DDESHARE = 0x2000,
/// <summary>Notify upon discarding</summary>
[Obsolete("Value is obsolete, but is provided for compatibility with 16-bit Windows.")]
GMEM_NOTIFY = 0x4000,
@ -844,6 +839,9 @@ namespace Vanara.PInvoke
/// <seealso cref="MemoryMethodsBase" />
public sealed class MoveableHGlobalMemoryMethods : MemoryMethodsBase
{
/// <inheritdoc/>
public override bool AllocZeroes => true;
/// <summary>Gets a value indicating whether this memory supports locking.</summary>
/// <value><see langword="true"/> if lockable; otherwise, <see langword="false"/>.</value>
public override bool Lockable => true;
@ -858,12 +856,12 @@ namespace Vanara.PInvoke
/// <summary>Frees the memory associated with a handle.</summary>
/// <param name="hMem">A memory handle.</param>
public override void FreeMem(IntPtr hMem) => GlobalFree(hMem);
public override void FreeMem(IntPtr hMem) => Win32Error.ThrowLastErrorIfNull((IntPtr)GlobalFree(hMem));
/// <summary>Locks the memory of a specified handle and gets a pointer to it.</summary>
/// <param name="hMem">A memory handle.</param>
/// <returns>A pointer to the locked memory.</returns>
public override IntPtr LockMem(IntPtr hMem) => GlobalLock(hMem);
public override IntPtr LockMem(IntPtr hMem) => Win32Error.ThrowLastErrorIfNull(GlobalLock(hMem));
/// <summary>Gets the reallocation method.</summary>
/// <param name="hMem">A memory handle.</param>
@ -875,9 +873,6 @@ namespace Vanara.PInvoke
/// <param name="hMem">A memory handle.</param>
/// <returns><see langword="true"/> if the memory object is still locked after decrementing the lock count; otherwise <see langword="false"/>.</returns>
public override bool UnlockMem(IntPtr hMem) => GlobalUnlock(hMem);
/// <inheritdoc/>
protected override bool AllocZeroes => true;
}
}
}

View File

@ -1,106 +1,174 @@
using NUnit.Framework;
using System;
using System.Runtime.InteropServices;
using System.Text;
using Vanara.Extensions;
using Vanara.InteropServices;
using static Vanara.PInvoke.Kernel32;
namespace Vanara.PInvoke.Tests
namespace Vanara.PInvoke.Tests;
[TestFixture]
public class MemoryApiTests
{
[TestFixture]
public class MemoryApiTests
// From https://docs.microsoft.com/en-us/windows/desktop/Memory/awe-example
[Test]
public void AWETest()
{
// From https://docs.microsoft.com/en-us/windows/desktop/Memory/awe-example
[Test]
public void AWETest()
const uint MEMORY_REQUESTED = 1024 * 1024;
GetSystemInfo(out SYSTEM_INFO sSysInfo); // fill the system information structure
TestContext.Write("This computer has page size {0}.\n", sSysInfo.dwPageSize);
// Calculate the number of pages of memory to request.
SizeT NumberOfPages = MEMORY_REQUESTED / sSysInfo.dwPageSize;
TestContext.Write("Requesting {0} pages of memory.\n", NumberOfPages);
// Enable the privilege.
using (SafeHPROCESS hProc = SafeHPROCESS.Current)
using (new ElevPriv("SeLockMemoryPrivilege", hProc))
{
const uint MEMORY_REQUESTED = 1024 * 1024;
// Allocate the physical memory.
IntPtr[] aPFNs = new IntPtr[NumberOfPages];
GetSystemInfo(out var sSysInfo); // fill the system information structure
SizeT NumberOfPagesInitial = NumberOfPages;
TestContext.Write("This computer has page size {0}.\n", sSysInfo.dwPageSize);
Assert.That(AllocateUserPhysicalPages(hProc, ref NumberOfPages, aPFNs), ResultIs.Successful);
// Calculate the number of pages of memory to request.
SizeT NumberOfPages = MEMORY_REQUESTED / sSysInfo.dwPageSize;
TestContext.Write("Requesting {0} pages of memory.\n", NumberOfPages);
Assert.That(NumberOfPagesInitial, Is.EqualTo(NumberOfPages));
// Enable the privilege.
using (var hProc = SafeHPROCESS.Current)
using (new ElevPriv("SeLockMemoryPrivilege", hProc))
// Reserve the virtual memory.
IntPtr lpMemReserved;
Assert.That(lpMemReserved = VirtualAlloc(IntPtr.Zero, MEMORY_REQUESTED, MEM_ALLOCATION_TYPE.MEM_RESERVE | MEM_ALLOCATION_TYPE.MEM_PHYSICAL, MEM_PROTECTION.PAGE_READWRITE), ResultIs.ValidHandle);
try
{
// Allocate the physical memory.
var aPFNs = new IntPtr[NumberOfPages];
// Map the physical memory into the window.
Assert.That(MapUserPhysicalPages(lpMemReserved, NumberOfPages, aPFNs), ResultIs.Successful);
var NumberOfPagesInitial = NumberOfPages;
// unmap
Assert.That(MapUserPhysicalPages(lpMemReserved, NumberOfPages, null), ResultIs.Successful);
Assert.That(AllocateUserPhysicalPages(hProc, ref NumberOfPages, aPFNs), ResultIs.Successful);
// Map the physical memory into the window scattered.
Assert.That(MapUserPhysicalPagesScatter(lpMemReserved, NumberOfPages, aPFNs), ResultIs.Successful);
Assert.That(NumberOfPagesInitial, Is.EqualTo(NumberOfPages));
// unmap
Assert.That(MapUserPhysicalPagesScatter(lpMemReserved, NumberOfPages, null), ResultIs.Successful);
// Reserve the virtual memory.
IntPtr lpMemReserved;
Assert.That(lpMemReserved = VirtualAlloc(IntPtr.Zero, MEMORY_REQUESTED, MEM_ALLOCATION_TYPE.MEM_RESERVE | MEM_ALLOCATION_TYPE.MEM_PHYSICAL, MEM_PROTECTION.PAGE_READWRITE), ResultIs.ValidHandle);
try
{
// Map the physical memory into the window.
Assert.That(MapUserPhysicalPages(lpMemReserved, NumberOfPages, aPFNs), ResultIs.Successful);
// unmap
Assert.That(MapUserPhysicalPages(lpMemReserved, NumberOfPages, null), ResultIs.Successful);
// Map the physical memory into the window scattered.
Assert.That(MapUserPhysicalPagesScatter(lpMemReserved, NumberOfPages, aPFNs), ResultIs.Successful);
// unmap
Assert.That(MapUserPhysicalPagesScatter(lpMemReserved, NumberOfPages, null), ResultIs.Successful);
// Free the physical pages.
Assert.That(FreeUserPhysicalPages(hProc, ref NumberOfPages, aPFNs), ResultIs.Successful);
}
finally
{
// Free virtual memory.
Assert.That(VirtualFree(lpMemReserved, 0, MEM_ALLOCATION_TYPE.MEM_RELEASE), ResultIs.Successful);
}
// Free the physical pages.
Assert.That(FreeUserPhysicalPages(hProc, ref NumberOfPages, aPFNs), ResultIs.Successful);
}
}
[Test]
public void FillMemoryTest()
{
using (var mem = new SafeHGlobalHandle(128))
finally
{
FillMemory((IntPtr)mem, 128, 0xFF);
Assert.That(mem.ToArray<byte>(128), Has.All.EqualTo(0xFF));
// Free virtual memory.
Assert.That(VirtualFree(lpMemReserved, 0, MEM_ALLOCATION_TYPE.MEM_RELEASE), ResultIs.Successful);
}
}
[Test]
public void GetLargePageMinimumTest()
{
Assert.That((uint)GetLargePageMinimum(), Is.GreaterThan(0));
}
[Test]
public void GetMemoryErrorHandlingCapabilitiesTest()
{
Assert.That(GetMemoryErrorHandlingCapabilities(out var cap), Is.True);
TestContext.WriteLine(cap);
}
[Test]
public void GetProcessWorkingSetSizeExTest()
{
Assert.That(GetProcessWorkingSetSizeEx(GetCurrentProcess(), out var min, out var max, out var flg), Is.True);
TestContext.WriteLine($"{min} : {max} : {flg}");
}
[Test]
public void GetSystemFileCacheSizeTest()
{
Assert.That(GetSystemFileCacheSize(out var min, out var max, out var flg), Is.True);
TestContext.WriteLine($"{min} : {max} : {flg}");
}
}
[Test]
public void FillMemoryTest()
{
using SafeHGlobalHandle mem = new(128);
FillMemory((IntPtr)mem, 128, 0xFF);
Assert.That(mem.ToArray<byte>(128), Has.All.EqualTo(0xFF));
}
[Test]
public void GetLargePageMinimumTest() => Assert.That((uint)GetLargePageMinimum(), Is.GreaterThan(0));
[Test]
public void GetMemoryErrorHandlingCapabilitiesTest()
{
Assert.That(GetMemoryErrorHandlingCapabilities(out uint cap), Is.True);
TestContext.WriteLine(cap);
}
[Test]
public void GetProcessWorkingSetSizeExTest()
{
Assert.That(GetProcessWorkingSetSizeEx(GetCurrentProcess(), out SizeT min, out SizeT max, out QUOTA_LIMITS_HARDWS flg), Is.True);
TestContext.WriteLine($"{min} : {max} : {flg}");
}
[Test]
public void GetSystemFileCacheSizeTest()
{
Assert.That(GetSystemFileCacheSize(out SizeT min, out SizeT max, out FILE_CACHE_LIMITS flg), Is.True);
TestContext.WriteLine($"{min} : {max} : {flg}");
}
[Test]
public void SafeMoveableHGlobleFlagsTest()
{
using SafeMoveableHGlobalHandle h = new(32);
Assert.AreEqual(GlobalFlags(h), GMEM.GMEM_SHARE);
}
[Test, Repeat(50)]
public void SafeMoveableHGlobalCreateFromHGLOBALTest()
{
var val = new RECT(8, 16, 32, 64);
var ptr = InteropExtensions.MarshalToPtr(val, i => (IntPtr)GlobalAlloc(GMEM.GHND | GMEM.GMEM_SHARE, i), out _, memLock: p => GlobalLock(p), memUnlock: p => GlobalUnlock(p));
using SafeMoveableHGlobalHandle h = new(ptr, true);
Assert.AreEqual(val, h.ToStructure<RECT>());
}
[Test]
public void SafeMoveableHGlobalCreateFromStringListTest()
{
var strings = new[] { "AAAA", "BBBB", "CCCC" };
using SafeMoveableHGlobalHandle h = SafeMoveableHGlobalHandle.CreateFromStringList(strings);
CollectionAssert.AreEqual(strings, h.ToStringEnum());
}
[Test]
public void SafeMoveableHGlobalCreateFromStructTest()
{
var val = new RECT(8, 16, 32, 64);
using SafeMoveableHGlobalHandle h = SafeMoveableHGlobalHandle.CreateFromStructure(val);
Assert.AreEqual(val, h.ToStructure<RECT>());
}
[Test]
public void SafeMoveableHGlobalCreateFromBytesTest()
{
const string txt = @"“00©0è0”";
using SafeMoveableHGlobalHandle h = new(System.Text.Encoding.Unicode.GetBytes(txt + '\0'));
Assert.AreEqual(txt, h.ToString(-1));
}
[Test, Repeat(50)]
public void SafeMoveableHGlobalTakeOwnershipTest()
{
var val = new RECT(8, 16, 32, 64);
using SafeMoveableHGlobalHandle h = SafeMoveableHGlobalHandle.CreateFromStructure(val);
HGLOBAL myh = h.TakeOwnership();
try
{
var p = GlobalLock(myh);
Assert.AreEqual(val, p.ToStructure<RECT>());
GlobalUnlock(myh);
}
finally { Assert.AreEqual(GlobalFree(myh), HGLOBAL.NULL); }
}
[Test]
public void SafeMoveableHGlobalWriteStructTest()
{
var val = new RECT(8, 16, 32, 64);
using SafeMoveableHGlobalHandle h = new(32);
h.Write(val);
Assert.AreEqual(val, h.ToStructure<RECT>());
}
[Test]
public void SafeMoveableHGlobalWriteStringListTest()
{
var strings = new[] { "AAAA", "BBBB", "CCCC" };
using SafeMoveableHGlobalHandle h = new(256);
h.CallLocked(p => p.Write(strings, StringListPackMethod.Concatenated, offset: 64, allocatedBytes: h.Size));
CollectionAssert.AreEqual(strings, h.ToStringEnum(prefixBytes: 64));
Assert.That(() => h.CallLocked(p => p.Write(strings, StringListPackMethod.Concatenated, offset: 250, allocatedBytes: h.Size)), Throws.Exception);
CollectionAssert.AreEqual(strings, h.ToStringEnum(prefixBytes: 64));
}
}