18

我正在通过Marshal.AllocHGlobal. 然后,我将一组字节复制到该位置,并将生成的内存段转换为 a 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在目标结构上使用)。我想尽可能有效地将这些悬挂区域归零。

就上下文而言,此代码正在反序列化从 Linux 上的 C++ 发送的高频多播消息。

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

// 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;
}

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


编辑

最后,我使用了leppie 的建议 gounsafe and using stackalloc。这分配了一个根据需要清零的字节数组,并将吞吐量从 50% 提高到 100%,具体取决于消息大小(更大的消息会带来更大的好处)。

最后的方法最终类似于:

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));
    }   
}

不幸的是,这仍然需要调用Marshal.PtrToStructure将字节转换为目标类型。

4

8 回答 8

18
[DllImport("kernel32.dll")]
static extern void RtlZeroMemory(IntPtr dst, UIntPtr length);
...
RtlZeroMemory(targetBytes, typeSize);
于 2009-09-28T13:35:50.267 回答
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);
    }
}
于 2011-11-19T21:48:40.453 回答
4

如果您使用的是 Net Core 或 NET5,您现在可以调用Unsafe.InitBlockUnaligned

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

对于不重要的数据大小,这比手动执行指针循环要快一个数量级,因为它使用平台特定的内在来实现全硬件加速。您可以从 kernel32 解决方案中受益,但可以跨平台,无需手动管理本机依赖项。

于 2020-05-23T23:41:12.940 回答
3

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

顺便说一句:我会去unsafe这里并使用 for 循环将额外的内存归零。

这也将使您受益于使用stackallocAllocGlobal.

于 2009-09-28T13:32:08.507 回答
3

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

在以下示例中,我在复制结构之前将缓冲区清零。

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); 
于 2009-09-28T13:34:26.703 回答
2

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

于 2009-09-28T13:28:54.030 回答
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 位写入而不是 8 位写入(最后几个字节仍然需要)要快几倍。

于 2014-05-08T00:31:33.543 回答
0

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

for(int i=0; i<buffSize; i++)
{
    Marshal.WriteByte(buffer, i, 0x00);
}
于 2013-02-01T18:19:51.533 回答