Pergunta

Estou alocar alguma memória não gerenciado no meu aplicativo via Marshal.AllocHGlobal. Estou em seguida, copiar um conjunto de bytes a este local e converter o segmento resultante da memória para um struct antes de liberar a memória novamente via Marshal.FreeHGlobal.

Aqui está o método:

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

Isso funciona para a maior parte, no entanto, se eu tiver menos bytes do que o tamanho do struct requer, então valores 'aleatória' são atribuídos aos últimos campos (estou usando LayoutKind.Sequential na struct alvo). Eu gostaria de zerar estes campos de suspensão da forma mais eficiente possível.

Para contexto, este código é deserializing de alta frequência mensagens de multicast enviados a partir de C ++ em Linux.

Aqui está um caso de teste falhar:

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

Executar esse teste repetidamente faz com que a segunda assert a falhar com um valor diferente de cada vez.


Editar

No final, eu usei leppie de sugestão de ir unsafe e usando stackalloc. Esta atribuição de uma matriz de bytes que foi zerada, conforme necessário, e melhor rendimento de entre 50% e 100%, dependendo do tamanho da mensagem (mensagens maiores vi maior benefício).

O método final acabou assemelhando-se:

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

Infelizmente isso ainda requer uma chamada para Marshal.PtrToStructure para converter os bytes para o tipo de destino.

Foi útil?

Solução

Por que não apenas verificar se start + length está dentro typesize?

BTW:. Eu iria apenas unsafe aqui e usar um loop for para zerar a memória adicional

Isso também lhe dará a vantagem de usar stackalloc que é muito mais seguro e mais rápido do que AllocGlobal.

Outras dicas

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

Este belo trabalho vontade no 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);
    }
}

Sim, como Jon Seigel disse, você pode zerar-lo usando Marshal.WriteByte

No exemplo a seguir, eu zerar o buffer antes de copiar a estrutura.

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

Eu nunca fiz essas coisas em C # antes, mas eu achei Marshal.WriteByte (IntPtr, Int32, Byte) no MSDN. Tente isso.

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

Eu acho que você vai encontrá-lo para ser várias vezes mais rápido usando 64 wrights bits em vez de 8 wrights bit (que você ainda precisa para os últimos bytes).

Eu acho que a melhor maneira de zero a um buffer é isso, se você não quer, ou não pode ir por outro caminho:

for(int i=0; i<buffSize; i++)
{
    Marshal.WriteByte(buffer, i, 0x00);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top