Comment convertir une structure en un tableau d'octets en C #?
J'ai défini une structure comme celle-ci:
public struct CIFSPacket
{
public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
public byte command;
public byte errorClass;
public byte reserved;
public ushort error;
public byte flags;
//Here there are 14 bytes of data which is used differently among different dialects.
//I do want the flags2. However, so I'll try parsing them.
public ushort flags2;
public ushort treeId;
public ushort processId;
public ushort userId;
public ushort multiplexId;
//Trans request
public byte wordCount;//Count of parameter words defining the data portion of the packet.
//From here it might be undefined...
public int parametersStartIndex;
public ushort byteCount; //Buffer length
public int bufferStartIndex;
public string Buffer;
}
Dans ma méthode principale, j'en crée une instance et lui affecte des valeurs:
CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;
packet.Buffer = "NT LM 0.12";
Maintenant, je veux envoyer ce paquet par socket. Pour cela, je dois convertir la structure en un tableau d'octets. Comment puis-je le faire?
Mon code complet est comme suit.
static void Main(string[] args)
{
Socket MyPing = new Socket(AddressFamily.InterNetwork,
SocketType.Stream , ProtocolType.Unspecified ) ;
MyPing.Connect("172.24.18.240", 139);
//Fake an IP Address so I can send with SendTo
IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
IPEndPoint IPEP = new IPEndPoint(IP, 139);
//Local IP for Receiving
IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
EndPoint EP = (EndPoint)Local;
CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;
packet.Buffer = "NT LM 0.12";
MyPing.SendTo(It takes byte array as parameter);
}
Que serait un extrait de code?
C'est assez facile, en utilisant le marshalling.
Haut de fichier
using System.Runtime.InteropServices
Une fonction
byte[] getBytes(CIFSPacket str) {
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
Et pour le reconvertir:
CIFSPacket fromBytes(byte[] arr) {
CIFSPacket str = new CIFSPacket();
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
Dans votre structure, vous devrez mettre ceci avant une chaîne
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;
Et assurez-vous que SizeConst est aussi gros que votre plus grande chaîne possible.
Et vous devriez probablement lire ceci: http://msdn.Microsoft.com/en-us/library/4ca6d5z7.aspx
Si vous voulez vraiment que ce soit rapide, vous pouvez le faire en utilisant un code dangereux avec CopyMemory. CopyMemory est environ 5 fois plus rapide (par exemple, 800 Mo de données prennent 3 secondes à copier via le marshalling, alors qu’il ne faut que 0,6 pour copier via CopyMemory). Cette méthode ne vous limite pas à utiliser uniquement des données qui sont réellement stockées dans le blob de struct lui-même, par exemple. nombres, ou tableaux d'octets de longueur fixe.
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
private static unsafe extern void CopyMemory(void *dest, void *src, int count);
private static unsafe byte[] Serialize(TestStruct[] index)
{
var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
fixed (void* d = &buffer[0])
{
fixed (void* s = &index[0])
{
CopyMemory(d, s, buffer.Length);
}
}
return buffer;
}
Regardez ces méthodes:
byte [] StructureToByteArray(object obj)
{
int len = Marshal.SizeOf(obj);
byte [] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(obj, ptr, true);
Marshal.Copy(ptr, arr, 0, len);
Marshal.FreeHGlobal(ptr);
return arr;
}
void ByteArrayToStructure(byte [] bytearray, ref object obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray,0, i,len);
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i);
}
Ceci est une copie éhontée d'un autre fil que j'ai trouvé sur Google!
Update: Pour plus de détails, consultez le source _
Variante du code de Vicent avec une allocation de mémoire en moins:
public static byte[] GetBytes<T>(T str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return arr;
}
public static T FromBytes<T>(byte[] arr) where T : struct
{
T str = default(T);
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return str;
}
J'utilise GCHandle
pour "épingler" la mémoire puis j'utilise directement son adresse avec h.AddrOfPinnedObject()
.
Comme la réponse principale utilise le type CIFSPacket, qui n'est pas (ou plus) disponible en C #, j'ai écrit les méthodes correctes
static byte[] getBytes(object str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
static T fromBytes<T>(byte[] arr)
{
T str = default(T);
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (T)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
Testés, ils travaillent.
Vous pouvez utiliser Marshal (StructureToPtr, ptrToStructure) et Marshal.copy mais ceci dépend de la plataform.
La sérialisation inclut des fonctions pour la sérialisation personnalisée.
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
SerializationInfo inclut des fonctions pour sérialiser chaque membre.
BinaryWriter et BinaryReader contiennent également des méthodes pour enregistrer/charger dans un tableau d'octets (flux).
Notez que vous pouvez créer un MemoryStream à partir d’un tableau d’octets ou un tableau d’octets à partir d’un MemoryStream.
Vous pouvez créer une méthode Save et une méthode New sur votre structure:
Save(Bw as BinaryWriter)
New (Br as BinaryReader)
Ensuite, vous sélectionnez les membres à enregistrer/charger dans le flux -> tableau d'octets.
Cela peut être fait très simplement.
Définissez explicitement votre structure avec [StructLayout(LayoutKind.Explicit)]
int size = list.GetLength(0);
IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
DataStruct *ptrBuffer = (DataStruct*)addr;
foreach (DataStruct ds in list)
{
*ptrBuffer = ds;
ptrBuffer += 1;
}
Ce code ne peut être écrit que dans un contexte dangereux. Vous devez libérer addr
lorsque vous avez terminé.
Marshal.FreeHGlobal(addr);
J'ai proposé une approche différente qui pourrait convertir any struct
sans le souci de fixer la longueur, mais le tableau d'octets résultant aurait un peu plus de temps système.
Voici un exemple struct
:
[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
public MyEnum enumvalue;
public string reqtimestamp;
public string resptimestamp;
public string message;
public byte[] rawresp;
}
Comme vous pouvez le constater, toutes ces structures nécessiteraient l’ajout des attributs de longueur fixe. Ce qui pouvait souvent finir par prendre plus de place que nécessaire. Notez que le LayoutKind.Sequential
est requis, car nous souhaitons que la réflexion nous donne toujours le même ordre lorsque vous extrayez pour FieldInfo
. Mon inspiration vient de TLV
Type-Longueur-Valeur. Regardons le code:
public static byte[] StructToByteArray<T>(T obj)
{
using (MemoryStream ms = new MemoryStream())
{
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream()) {
bf.Serialize(inms, info.GetValue(obj));
byte[] ba = inms.ToArray();
// for length
ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));
// for value
ms.Write(ba, 0, ba.Length);
}
}
return ms.ToArray();
}
}
La fonction ci-dessus utilise simplement la variable BinaryFormatter
pour sérialiser la taille inconnue brute object
, et je garde simplement une trace de la taille également et la stocke également dans la sortie MemoryStream
.
public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
output = (T) Activator.CreateInstance(typeof(T), null);
using (MemoryStream ms = new MemoryStream(data))
{
byte[] ba = null;
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
// for length
ba = new byte[sizeof(int)];
ms.Read(ba, 0, sizeof(int));
// for value
int sz = BitConverter.ToInt32(ba, 0);
ba = new byte[sz];
ms.Read(ba, 0, sz);
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream(ba))
{
info.SetValue(output, bf.Deserialize(inms));
}
}
}
}
Lorsque nous voulons le reconvertir en sa struct
originale, nous lisons simplement la longueur en arrière et la rejetons directement dans la BinaryFormatter
qui à son tour la restitue dans la struct
.
Ces 2 fonctions sont génériques et devraient fonctionner avec toutes les variables struct
. J'ai testé le code ci-dessus dans mon projet C#
, dans lequel j'ai un serveur et un client, connectés et communiquant via NamedPipeStream
et je transmets ma struct
sous forme de tableau d'octets d'un à un autre et le reconverti.
Je pense que mon approche serait peut-être meilleure, car elle ne fixe pas la longueur de la variable struct
elle-même et le seul surdébit est simplement une variable int
pour chaque champ de votre structure. Le tableau d'octets généré par BinaryFormatter
présente également une petite charge supplémentaire, mais à part cela, ce n'est pas beaucoup.
Je voudrais jeter un oeil aux classes BinaryReader et BinaryWriter. J'ai récemment dû sérialiser les données sur un tableau d'octets (et inversement) et je n'ai trouvé ces classes qu'après les avoir moi-même réécrites.
http://msdn.Microsoft.com/en-us/library/system.io.binarywriter.aspx
Il y a aussi un bon exemple sur cette page.
Ressemble à une structure prédéfinie (niveau C) pour une bibliothèque externe. Le maréchal est votre ami. Vérifier:
http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx
pour un débutant comment faire face à cela. Notez que vous pouvez, avec des attributs, définir des éléments tels que la disposition des octets et la gestion des chaînes. TRÈS belle approche, en fait.
Ni BinaryFormatter, ni MemoryStream ne sont faits pour cela.
@Abdel Olakara answer ne fonctionne pas dans .net 3.5, devrait être modifié comme suit:
public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray, 0, i, len);
obj = (T)Marshal.PtrToStructure(i, typeof(T));
Marshal.FreeHGlobal(i);
}
Cet exemple s’applique uniquement aux types blittables purs, par exemple les types qui peuvent être mémorisés directement en C.
Exemple - structure 64 bits bien connue
[StructLayout(LayoutKind.Sequential)]
public struct Voxel
{
public ushort m_id;
public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}
Définie exactement comme cela, la structure sera automatiquement compressée au format 64 bits.
Nous pouvons maintenant créer un volume de voxels:
Voxel[,,] voxels = new Voxel[16,16,16];
Et sauvegardez-les tous dans un tableau d'octets:
int size = voxels.Length * 8; // Well known size: 64 bits
byte[] saved = new byte[size];
GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
h.Free();
// now feel free to save 'saved' to a File / memory stream.
Cependant, puisque le PO veut savoir comment convertir la structure elle-même, notre structure Voxel peut avoir la méthode suivante ToBytes
:
byte[] bytes = new byte[8]; // Well known size: 64 bits
GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
h.Free();
Header header = new Header();
Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
Cela devrait faire l'affaire rapidement, non?