如何清除Marshal.AllocHGlobal分配的内存?

时间:2009-09-28 13:22:27

标签: .net memory unmanaged-memory

我通过Marshal.AllocHGlobal在我的应用程序中分配一些非托管内存。然后,我将一组字节复制到此位置,并将生成的内存段转换为struct,然后再通过Marshal.FreeHGlobal释放内存。

以下是方法:

public static T Deserialize<T>(byte[] messageBytes, int start, int length)
    where T : struct
{
    if (start + length > messageBytes.Length)
        throw new ArgumentOutOfRangeException();

    int typeSize = Marshal.SizeOf(typeof(T));
    int bytesToCopy = Math.Min(typeSize, length);

    IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);
    Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);

    if (length < typeSize)
    {
        // Zero out additional bytes at the end of the struct
    }

    T item = (T)Marshal.PtrToStructure(targetBytes, typeof(T));
    Marshal.FreeHGlobal(targetBytes);
    return item;
}

这在大多数情况下都有效,但是如果我的字节数少于struct所需的字节数,则会将“随机”值分配给最后的字段(我在LayoutKind.Sequential上使用// Give only one byte, which is too few for the struct var s3 = MessageSerializer.Deserialize<S3>(new[] { (byte)0x21 }); Assert.AreEqual(0x21, s3.Byte); Assert.AreEqual(0x0000, s3.Int); // hanging field should be zero, but isn't [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] private struct S3 { public byte Byte; public int Int; } 目标结构)。我想尽可能有效地将这些悬挂的区域归零。

对于上下文,此代码对从Linux上的C ++发送的高频多播消息进行反序列化。

这是一个失败的测试用例:

unsafe

重复运行此测试会导致第二个断言失败并且每次都有不同的值。


修改

最后,我使用stackalloc leppie's suggestion并使用public static T Deserialize<T>(byte[] messageBytes, int startIndex, int length) where T : struct { if (length <= 0) throw new ArgumentOutOfRangeException("length", length, "Must be greater than zero."); if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex", startIndex, "Must be greater than or equal to zero."); if (startIndex + length > messageBytes.Length) throw new ArgumentOutOfRangeException("length", length, "startIndex + length must be <= messageBytes.Length"); int typeSize = Marshal.SizeOf(typeof(T)); unsafe { byte* basePtr = stackalloc byte[typeSize]; byte* b = basePtr; int end = startIndex + Math.Min(length, typeSize); for (int srcPos = startIndex; srcPos < end; srcPos++) *b++ = messageBytes[srcPos]; return (T)Marshal.PtrToStructure(new IntPtr(basePtr), typeof(T)); } } 。这分配了一个根据需要归零的字节数组,并根据消息大小提高了50%到100%之间的吞吐量(更大的消息看到更大的好处)。

最终方法最终类似:

Marshal.PtrToStructure

不幸的是,仍需要调用{{1}}将字节转换为目标类型。

8 个答案:

答案 0 :(得分:14)

[DllImport("kernel32.dll")]
static extern void RtlZeroMemory(IntPtr dst, int length);
...
RtlZeroMemory(targetBytes, typeSize);

答案 1 :(得分:6)

这在Windows上可以正常工作:

namespace KernelPInvoke
{
    /// <summary>
    /// Implements some of the C functions declared in string.h
    /// </summary>
    public static class MemoryWrapper
    {
        [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
        static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);

        [DllImport("kernel32.dll", EntryPoint = "MoveMemory", SetLastError = false)]
        static extern void MoveMemory(IntPtr destination, IntPtr source, uint length);

        [DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", SetLastError = false)]
        static extern void FillMemory(IntPtr destination, uint length, byte fill);
    }

    var ptr = Marshal.AllocHGlobal(size);
    try
    {
        MemoryWrapper.FillMemory(ptr, size, 0);
        // further work...
    }
    finally
    {
        Marshal.FreeHGlobal(ptr);
    }
}

答案 2 :(得分:3)

为什么不检查start + length是否在typesize范围内?

BTW:我会在这里unsafe并使用for循环将额外的内存归零。

这也将为您提供使用stackalloc的好处,AllocGlobal比{{1}}更安全,更快。

答案 3 :(得分:3)

是的,Jon Seigel说,您可以使用Marshal.WriteByte将其归零

在下面的示例中,我在复制struct之前将缓冲区清零。

if (start + length > messageBytes.Length) 
    throw new ArgumentOutOfRangeException();   
int typeSize = Marshal.SizeOf(typeof(T));    
int bytesToCopy = Math.Min(typeSize, length);   
IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);  
//zero out buffer
for(int i=0; i < typeSize; i++)
{
    Marshal.WriteByte(targetBytes, i, 0);
}
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy); 

答案 4 :(得分:2)

之前我从未在C#中做过这些事情,但我在MSDN中找到了Marshal.WriteByte(IntPtr,Int32,Byte)。试试吧。

答案 5 :(得分:2)

for(int i=0; i < buffSize / 8; i += 8 )
{
    Marshal.WriteInt64(buffer, i, 0x00);
}

for(int i= buffSize % 8 ; i < -1 ; i-- )
{
    Marshal.WriteByte (buffer, buffSize - i, 0x00);
}

我认为使用64位wrights而不是8位wrights(最后几个字节仍然需要),你会发现它快几倍。

答案 6 :(得分:1)

如果您使用的是Net Core,现在只需执行以下操作:

Unsafe.InitBlockUnaligned((byte*)ptr, 0, byteCount) 

https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe.initblockunaligned?view=netcore-3.1

对于任何琐碎的数据大小,这比手动执行指针循环快一个数量级,因为它使用特定于平台的内在函数来进行完整的硬件加速。您可以获得kernel32解决方案的好处,但可以跨平台运行,而无需手动管理本机依赖项。

答案 7 :(得分:-1)

我认为将缓冲区归零的最佳方法是,如果您不想要,或者不能采取其他方式:

for(int i=0; i<buffSize; i++)
{
    Marshal.WriteByte(buffer, i, 0x00);
}