2017-06-26 2 views
1

J'ai donc créé une structure que je veux envoyer en utilisant un simple DatagramSocket.La structure hachée aboutit à OutOfBoundsException en C#

Le code struct est la suivante:

public struct MsgData 
{ 
    private readonly int _value; 
    private readonly string _descr; 
    public MsgData(string desc, int value) 
    { 
     _descr = desc; 
     _value = value; 
    } 

    public int GetValue() 
    { 
     return _value; 
    } 

    public string GetDescr() 
    { 
     return _descr; 
    } 
} 

Je procède par la conversion en un tableau d'octets comme ceci:

public static byte[] GetBytes(MsgData message) 
    { 
     var size = Marshal.SizeOf(message); 
     var data = new byte[size]; 

     System.IntPtr ptr = Marshal.AllocHGlobal(size); 
     Marshal.StructureToPtr(message, ptr, true); 
     Marshal.Copy(ptr, data, 0, size); 
     Marshal.FreeHGlobal(ptr); 

     return data; 
    } 

et le retourner à un struct comme MSGDATA si:

public static MsgData GetMessage(byte[] bytes) 
    { 
     var data = new MsgData(); 

     var size = Marshal.SizeOf(data); 
     var ptr = Marshal.AllocHGlobal(size); 

     Marshal.Copy(bytes, 0, ptr, size); 

     data = Marshal.PtrToStructure<MsgData>(ptr); 
     Marshal.FreeHGlobal(ptr); 

     return data; 
    } 

Cependant, je reçois un:

System.ArgumentOutOfRangeException: 'Requested range extends past the end of the array.' 

en essayant de convertir en ligne:

Marshal.Copy(bytes, 0, ptr, size); 

Je vais aller avec un simple sérialisation instad maintenant, mais je me demande pourquoi cela ne fonctionne pas comme prévu?

+0

Qu'est-ce que est 'bytes.Length' quand l'exception se produit? – mjwills

+0

Etes-vous sûr que 'bytes' contient suffisamment d'octets? J'ajouterais 'if (bytes.Length! = Size) throw new ArgumentException();' quelque part. – Groo

+0

Il n'est pas clair ce que vous voulez que ce code fasse. D'abord, vous n'avez pas spécifié l'attribut StructLayout sur la structure, auquel cas l'empaquetage n'est pas spécifié (vous pouvez détecter quel emballage sera utilisé en fonction de x86/x64 mais le code apparaîtra comme fragile). De plus, vous ne spécifiez pas les attributs "MarshalAs" sur vos champs, auquel cas votre chaîne ne sera pas vraiment rassemblée. Enfin, vous passez "true" au paramètre fDeleteOld de StructureToPtr mais votre mémoire allouée ne doit pas contenir de structure par défaut. –

Répondre

2

Il y a plusieurs problèmes avec votre code alors laissez-moi vous expliquer ce que vous devez faire pour le réparer. D'abord, votre structure a une disposition optimisée pour un accès rapide à la mémoire. Lorsque vous placez ceci dans un tableau d'octets, vous allez, par défaut, copier cette disposition de la mémoire. Votre appel Marshal.SizeOf(...) reflète cela. Il retourne 16, toujours. Comment cela peut-il être? Comment votre chaîne peut-elle être rassemblée à quelque chose dans ces 16 octets, même si la chaîne est beaucoup plus longue que 16?

La réponse est que ce n'est pas le cas. Au lieu de cela, vous placez le pointeur sur l'objet chaîne sous la forme d'octets. 16 octets est 8 octets pour l'int (4 pour l'int + 4 pour le remplissage pour aligner la valeur suivante sur une limite d'adresse mémoire de 8 octets, je cours 64 bits), puis 8 octets pour le référence de chaîne (adresse).

Alors, que faut-il faire? Vous avez besoin pour décorer votre structure à quelques attributs pour indiquer au moteur de marshalling comment y faire face:

[StructLayout(LayoutKind.Sequential, Pack=0)] 
public struct MsgData 
{ 
    [MarshalAs(UnmanagedType.I4)] 
    private readonly int _value; 

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)] 
    private readonly string _descr; 

    ... rest as you have it 

Après avoir fait cela, vous obtiendrez cette taille de Marshal.SizeOf(...): 68.

68 = 4 octets pour l'int + 64 pour la chaîne.

Notez que lors du rassemblement, les résultats de taille dynamique ne sont pas vraiment faciles à gérer. Définir une limite supérieure sur la chaîne est donc le bon choix.


Cependant, il y a une solution beaucoup plus facile à votre problème, utilisez le construit en sérialisation binaire dans .NET.

Voici deux nouvelles versions de vos Get* méthodes qui ne vous oblige pas à faire marshalling:

public static byte[] GetBytes(MsgData message) 
{ 
    using (var stream = new MemoryStream()) 
    { 
     new BinaryFormatter().Serialize(stream, message); 
     return stream.ToArray(); 
    } 
} 

public static MsgData GetMessage(byte[] bytes) 
{ 
    using (var stream = new MemoryStream(bytes)) 
    { 
     return (MsgData)new BinaryFormatter().Deserialize(stream); 
    } 
} 

Notez que vous devrez appliquer la SerializableAttribute à votre struct:

[Serializable] 
public struct MsgData 
{ 
+0

Cette dernière partie était ce que je voulais dire par "simple sérialisation" dans ce cas. Cependant je vis et j'apprends. Merci pour l'explication approfondie et pédagogique. – sebrock