Question

J'allouons une mémoire non gérée dans mon application via Marshal.AllocHGlobal. Je puis copier un ensemble d'octets à cet endroit et à convertir le segment résultant de la mémoire à un struct avant de libérer la mémoire de nouveau via Marshal.FreeHGlobal.

Voici la méthode:

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

Cela fonctionne pour la plupart, si je moins d'octets que la taille de la struct nécessite, alors les valeurs « aléatoires » sont attribuées aux derniers champs (je me sers LayoutKind.Sequential sur la struct cible). Je voudrais à zéro ces domaines suspendus le plus efficacement possible.

Pour le contexte, ce code est désérialisation à haute fréquence des messages de multidiffusion envoyés à partir de C ++ sous Linux.

Voici un test à défaut:

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

exécution de ce test fait à plusieurs reprises la deuxième assert à l'échec d'une valeur différente à chaque fois.


EDIT

En fin de compte, je leppie de suggestion d'aller unsafe et en utilisant stackalloc. Ce alloué un tableau d'octets qui a été remis à zéro en fonction des besoins, et un débit amélioré entre 50% et 100%, en fonction de la taille du message (messages plus grands avantages vu plus).

La dernière méthode a fini par ressembler à:

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

Malheureusement, cela nécessite encore un appel à Marshal.PtrToStructure pour convertir les octets dans le type de cible.

Était-ce utile?

La solution

Pourquoi ne pas simplement vérifier si start + length est dans typesize?

BTW:. Je voudrais juste aller unsafe ici et utiliser une boucle pour mettre à zéro la mémoire supplémentaire

Ce trop vous donnera l'avantage d'utiliser stackalloc qui est beaucoup plus sûr et plus rapide que AllocGlobal.

Autres conseils

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

Cela fonctionne bien sous 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);
    }
}

Oui comme Jon Seigel dit, vous pouvez zéro à l'aide de ce Marshal.WriteByte

Dans l'exemple suivant, je zéro de la mémoire tampon avant de copier le 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); 

Je ne l'ai jamais fait ce genre de choses en C #, mais je trouve Marshal.WriteByte (IntPtr, Int32, octet) dans MSDN. Essayez cela.

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

Je pense que vous trouverez qu'il est plusieurs fois plus rapide en utilisant 64 bits Wrights au lieu de 8 bits Wrights (dont vous avez besoin encore quelques octets).

Je pense que la meilleure façon de mettre à zéro un tampon est-ce, si vous ne voulez pas ou ne pouvez pas aller dans l'autre sens:

for(int i=0; i<buffSize; i++)
{
    Marshal.WriteByte(buffer, i, 0x00);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top