Un Guide Complet pour Lire des Structures de Données C/C++ en C# à partir d’un Tableau byte[]
Lors de la migration ou du travail avec des structures de données entre langages, en particulier de C/C++ vers C#, les développeurs sont souvent confrontés au défi de traduire la représentation en byte d’une structure C/C++ en un équivalent C# gérable. Cet article aborde comment convertir efficacement un tableau byte[]
contenant des données de structure C/C++ en une structure C#, sans la maladresse qui découle souvent d’une copie de données inutile.
Compréhension du Problème
Considérons la définition suivante d’une structure C :
typedef OldStuff {
CHAR Name[8];
UInt32 User;
CHAR Location[8];
UInt32 TimeStamp;
UInt32 Sequence;
CHAR Tracking[16];
CHAR Filler[12];
}
Cette représentation se compose d’une séquence fixe de caractères et d’entiers. Lorsqu’ils travaillent avec de telles données binaires en C#, les développeurs font face au défi d’aligner avec précision la structure et de la convertir pour une utilisation dans du code géré.
La structure C# correspondante utilise StructLayout
pour contrôler la manière dont les données sont organisées en mémoire :
[StructLayout(LayoutKind.Explicit, Size = 56, Pack = 1)]
public struct NewStuff
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
[FieldOffset(0)]
public string Name;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(8)]
public uint User;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
[FieldOffset(12)]
public string Location;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(20)]
public uint TimeStamp;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(24)]
public uint Sequence;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
[FieldOffset(28)]
public string Tracking;
}
L’Approche Conventionnelle
Une approche typique que les développeurs adoptent consiste à copier le tableau de bytes dans un tampon séparé avant d’essayer de le marshaller dans la structure C# désirée. Voici une méthode couramment rencontrée :
GCHandle handle;
NewStuff MyStuff;
int BufferSize = Marshal.SizeOf(typeof(NewStuff));
byte[] buff = new byte[BufferSize];
Array.Copy(SomeByteArray, 0, buff, 0, BufferSize);
handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
MyStuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));
handle.Free();
Bien que cette approche fonctionne, elle introduit une surcharge inutile, en particulier en ce qui concerne l’utilisation de mémoire et de performances, en créant une copie intermédiaire des données.
Une Meilleure Solution
Au lieu de dupliquer les données, vous pouvez utiliser directement le tableau de bytes pour extraire votre structure. Voici une méthode plus efficace :
Utilisation Directe de GCHandle
NewStuff ByteArrayToNewStuff(byte[] bytes)
{
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
NewStuff stuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));
return stuff;
}
finally
{
handle.Free();
}
}
Cette méthode alloue un handle au tableau de bytes d’origine et élimine la nécessité de créer un tampon supplémentaire.
Méthode Générique pour la Flexibilité
Si vous souhaitez convertir n’importe quelle structure d’un tableau de bytes, vous pouvez créer une méthode générique :
T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
T stuff;
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
}
finally
{
handle.Free();
}
return stuff;
}
Approche Non-Sécurisée pour une Performance Accrue
Pour les utilisateurs avancés, utiliser le contexte unsafe
peut offrir une performance encore meilleure, car cela vous permet de travailler directement avec des pointeurs :
unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
fixed (byte* ptr = &bytes[0])
{
return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
}
}
Conclusion
Lire efficacement une structure de données C/C++ en C# à partir d’un tableau byte[]
ne doit pas être compliqué. En utilisant directement le GCHandle
sur le tableau de bytes ou en utilisant du code unsafe
, vous améliorez les performances et simplifiez votre code. L’adoption de ces approches conduit à un code plus propre, plus maintenable tout en évitant une surcharge de mémoire inutile.
Ainsi, la prochaine fois que vous devrez traiter une conversion de données entre langages, rappelez-vous de ces méthodes pour rationaliser votre processus !