7 votes

Écriture de structs emballés avec un bug de MemoryMappedViewAccessor ?

J'ai une structure qui est définie avec Pack=1 et il fait 29 octets de long. S'il n'était pas emballé, sa longueur serait de 32 octets.

  • Marshal.SizeOf(TypeOf(StructName)) retourne 29.

  • StructName struct; sizeof(struct) retourne 32.

Lorsque j'écris cette structure en utilisant MemoryMappedViewAccessor il écrit 32 octets, PAS 29 octets.

Donc, à moins de transformer la structure en un tableau d'octets et de l'écrire de cette façon, y a-t-il un moyen de faire en sorte que la structure soit écrite correctement ?

Plus de détails : si vous utilisez la mise en page explicite, Write écrira, en fait, 29 octets. WriteArray, en revanche, écrit 32 octets pour chaque élément.

Et avip Oui, la sérialisation méticuleuse des octets fonctionnera probablement, mais (je ne l'ai pas profilé mais je le suppose) elle est probablement plus lente de plusieurs ordres de grandeur qu'un WriteArray, non ?

6voto

avip Points 517

Modifier : OK, j'ai finalement compris ce que vous demandez vraiment. Nous n'utilisons généralement pas MemoryMappedViewAccessor pour sérialiser des objets, et maintenant vous savez pourquoi.

Ce qui suit vous donnera le résultat escompté.

public static class ByteSerializer
{
    public static Byte[] Serialize<T>(IEnumerable<T> msg) where T : struct
    {
        List<byte> res = new List<byte>();
        foreach (var s in msg)
        {
            res.AddRange(Serialize(s));
        }
        return res.ToArray();
    }

    public static Byte[] Serialize<T>(T msg) where T : struct
    {
        int objsize = Marshal.SizeOf(typeof(T));
        Byte[] ret = new Byte[objsize];

        IntPtr buff = Marshal.AllocHGlobal(objsize);
        Marshal.StructureToPtr(msg, buff, true);
        Marshal.Copy(buff, ret, 0, objsize);
        Marshal.FreeHGlobal(buff);
        return ret;
    }
}

class Program
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct Yours
    {
        public Int64 int1;
        public DateTime dt1;
        public float f1;
        public float f2;
        public float f3;
        public byte b;
    }

    static void Main()
    {
        var file = @"c:\temp\test.bin";
        IEnumerable<Yours> t = new Yours[3];
        File.WriteAllBytes(file, ByteSerializer.Serialize(t));

        using (var stream = File.OpenRead(file))
        {
            Console.WriteLine("file size: " + stream.Length);
        }
    }
}

EDITAR : Il semble donc que DateTime aime vraiment être sur une adresse mémoire alignée. Bien que vous puissiez définir la disposition explicite, je pense qu'une approche plus simple serait :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Test
{
    private long dt1; 
    public byte b;
    public Int64 int1;
    public float f1;
    public float f2;
    public float f3;

    public DateTime DT
    {
        get { return new DateTime(dt1); }
        set { dt1 = value.Ticks; }
    }
}

Mais je ne vois pas pourquoi vous devriez vous soucier de la représentation de la mémoire gérée.

Alternativement, [StructLayout(LayoutKind.Explicit)] devrait empêcher l'alignement de la mémoire.

Exemple (l'expression "taille gérée" est tirée du document suivant ce poste )

[StructLayout(LayoutKind.Explicit, Size = 9)]
public struct Test
{
    [FieldOffset(0)]
    public DateTime dt1;
    [FieldOffset(8)]
    public byte b;
}

class Program
{
    static readonly Func<Type, uint> SizeOfType = (Func<Type, uint>)Delegate.CreateDelegate(typeof(Func<Type, uint>), typeof(Marshal).GetMethod("SizeOfType", BindingFlags.NonPublic | BindingFlags.Static));

    static void Main()
    {
        Test t = new Test() { dt1 = DateTime.MaxValue, b = 42 };
        Console.WriteLine("Managed size: " + SizeOfType(typeof(Test)));
        Console.WriteLine("Unmanaged size: " + Marshal.SizeOf(t));
        using (MemoryMappedFile file = MemoryMappedFile.CreateNew(null, 1))
        using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor())
        {
            accessor.Write(0L, ref t);
            long pos = 0;

            for (int i = 0; i < 9; i++)
                Console.Write("|" + accessor.ReadByte(pos++));
            Console.Write("|\n");
        }
    }
}

Sortie :

Managed size: 9
Unmanaged size: 9
|255|63|55|244|117|40|202|43|42|   // managed memory layout is as expected

BTW, DateTime semble rompre le contrat séquentiel - mais n'oubliez pas que le contrat concerne la carte mémoire Marshaled. Il n'y a aucune spécification concernant la disposition de la mémoire gérée.

[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 9)]
public struct Test
{
    public DateTime dt1;
    public byte b;
}

Et la sortie du code ci-dessus :

Managed size: 12
Unmanaged size: 9
|42|0|0|0|255|63|55|244|117|40|202|43|   // finally found those 3 missing bytes :-)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X