Organizando uma coleção de bytes big-endian em uma estrutura para extrair valores

Há uma pergunta perspicaz sobre a leitura de uma estrutura de dados C / C ++ em C # de uma matriz de bytes , mas não consigo fazer com que o código funcione para minha coleção de bytes big-endian (ordem de bytes de rede). (EDIT: Observe que minha estrutura real tem mais do que apenas um campo.) Existe uma maneira de empacotar os bytes em uma versão big-endian da estrutura e, em seguida, extrair os valores no endianness da estrutura (que do host , que geralmente é little-endian)?

(Observe que a reversão da matriz de bytes não funcionará – os bytes de cada valor devem ser invertidos, o que não fornece a mesma coleção que reverte todos os bytes.)

Isso deve resumir o que estou procurando (LE = LittleEndian, BE = BigEndian):

void Main() { var leBytes = new byte[] {1, 0, 2, 0}; var beBytes = new byte[] {0, 1, 0, 2}; Foo fooLe = ByteArrayToStructure(leBytes); Foo fooBe = ByteArrayToStructureBigEndian(beBytes); Assert.AreEqual(fooLe, fooBe); } [StructLayout(LayoutKind.Explicit, Size=4)] public struct Foo { [FieldOffset(0)] public ushort firstUshort; [FieldOffset(2)] public ushort secondUshort; } T ByteArrayToStructure(byte[] bytes) where T: struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T)); handle.Free(); return stuff; } T ByteArrayToStructureBigEndian(byte[] bytes) where T: struct { ??? } 

Outros links úteis:

Byte de uma estrutura e para preocupações endian

Um pouco mais sobre bytes e endianness (ordem de bytes)

Leia arquivos binários com mais eficiência usando c #

Inseguro e lendo de arquivos

A contribuição de Mono para a questão

Dominando estruturas C #

Aqui está outra solução para trocar o endianness.

É ajustado a partir da solução Adam Robinsons aqui: https://stackoverflow.com/a/2624377/1254743

É ainda capaz de lidar com estruturas aninhadas.

 public static class FooTest { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo2 { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; public Foo2 foo2; } public static void test() { Foo2 sample2 = new Foo2() { b1 = 0x01, s = 0x0203, S = 0x0405, i = 0x06070809, I = 0x0a0b0c0d, l = 0xe0f101112131415, L = 0x161718191a1b1c, f = 1.234f, d = 4.56789, MyString = @"123456789", // null terminated => only 9 characters! }; Foo sample = new Foo() { b1 = 0x01, s = 0x0203, S = 0x0405, i = 0x06070809, I = 0x0a0b0c0d, l = 0xe0f101112131415, L = 0x161718191a1b1c, f = 1.234f, d = 4.56789, MyString = @"123456789", // null terminated => only 9 characters! foo2 = sample2, }; var bytes_LE = Dummy.StructToBytes(sample, Endianness.LittleEndian); var restoredLEAsLE = Dummy.BytesToStruct(bytes_LE, Endianness.LittleEndian); var restoredLEAsBE = Dummy.BytesToStruct(bytes_LE, Endianness.BigEndian); var bytes_BE = Dummy.StructToBytes(sample, Endianness.BigEndian); var restoredBEAsLE = Dummy.BytesToStruct(bytes_BE, Endianness.LittleEndian); var restoredBEAsBE = Dummy.BytesToStruct(bytes_BE, Endianness.BigEndian); Debug.Assert(sample.Equals(restoredLEAsLE)); Debug.Assert(sample.Equals(restoredBEAsBE)); Debug.Assert(restoredBEAsLE.Equals(restoredLEAsBE)); } public enum Endianness { BigEndian, LittleEndian } private static void MaybeAdjustEndianness(Type type, byte[] data, Endianness endianness, int startOffset = 0) { if ((BitConverter.IsLittleEndian) == (endianness == Endianness.LittleEndian)) { // nothing to change => return return; } foreach (var field in type.GetFields()) { var fieldType = field.FieldType; if (field.IsStatic) // don't process static fields continue; if (fieldType == typeof(string)) // don't swap bytes for strings continue; var offset = Marshal.OffsetOf(type, field.Name).ToInt32(); // handle enums if (fieldType.IsEnum) fieldType = Enum.GetUnderlyingType(fieldType); // check for sub-fields to recurse if necessary var subFields = fieldType.GetFields().Where(subField => subField.IsStatic == false).ToArray(); var effectiveOffset = startOffset + offset; if (subFields.Length == 0) { Array.Reverse(data, effectiveOffset, Marshal.SizeOf(fieldType)); } else { // recurse MaybeAdjustEndianness(fieldType, data, endianness, effectiveOffset); } } } internal static T BytesToStruct(byte[] rawData, Endianness endianness) where T : struct { T result = default(T); MaybeAdjustEndianness(typeof(T), rawData, endianness); GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T)); } finally { handle.Free(); } return result; } internal static byte[] StructToBytes(T data, Endianness endianness) where T : struct { byte[] rawData = new byte[Marshal.SizeOf(data)]; GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); Marshal.StructureToPtr(data, rawDataPtr, false); } finally { handle.Free(); } MaybeAdjustEndianness(typeof(T), rawData, endianness); return rawData; } } 

Parece que deve haver uma solução mais elegante, mas isso deve, pelo menos, fazer você funcionar:

  static T ByteArrayToStructureBigEndian(byte[] bytes) where T : struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); handle.Free(); System.Type t = stuff.GetType(); FieldInfo[] fieldInfo = t.GetFields(); foreach (FieldInfo fi in fieldInfo) { if (fi.FieldType == typeof(System.Int16)) { // TODO } else if (fi.FieldType == typeof(System.Int32)) { // TODO } else if (fi.FieldType == typeof(System.Int64)) { // TODO } else if (fi.FieldType == typeof(System.UInt16)) { UInt16 i16 = (UInt16)fi.GetValue(stuff); byte[] b16 = BitConverter.GetBytes(i16); byte[] b16r = b16.Reverse().ToArray(); fi.SetValueDirect(__makeref(stuff), BitConverter.ToUInt16(b16r, 0); } else if (fi.FieldType == typeof(System.UInt32)) { // TODO } else if (fi.FieldType == typeof(System.UInt64)) { // TODO } } return stuff; } 

Como aludido em meu comentário sobre a resposta de @ weismat, há uma maneira fácil de conseguir a estruturação big-endian. Envolve uma reversão dupla: os bytes originais são totalmente invertidos, então a estrutura em si é a reversão do formato de dados original (big-endian).

O fooLe e fooBe no Main terão os mesmos valores para todos os campos. (Normalmente, a estrutura e os bytes little-endian não estariam presentes, é claro, mas isso mostra claramente a relação entre as ordens de bytes).

OBSERVAÇÃO: Veja o código atualizado, incluindo como obter bytes de volta da estrutura.

 public void Main() { var beBytes = new byte[] { 0x80, 0x80,0, 0x80,0, 0x80,0,0,0, 0x80,0,0,0, 0x80,0,0,0,0,0,0,0, 0x80,0,0,0,0,0,0,0, 0x3F,0X80,0,0, // float of 1 (see http://en.wikipedia.org/wiki/Endianness#Floating-point_and_endianness) 0x3F,0xF0,0,0,0,0,0,0, // double of 1 0,0,0,0x67,0x6E,0x69,0x74,0x73,0x65,0x54 // Testing\0\0\0 }; var leBytes = new byte[] { 0x80, 0,0x80, 0,0x80, 0,0,0,0x80, 0,0,0,0x80, 0,0,0,0,0,0,0,0x80, 0,0,0,0,0,0,0,0x80, 0,0,0x80,0x3F, // float of 1 0,0,0,0,0,0,0xF0,0x3F, // double of 1 0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0 }; Foo fooLe = ByteArrayToStructure(leBytes).Dump("LE"); FooReversed fooBe = ByteArrayToStructure(beBytes.Reverse().ToArray()).Dump("BE"); } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FooReversed { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; public double d; public float f; public ulong L; public long l; public uint I; public int i; public ushort S; public short s; public byte b1; } T ByteArrayToStructure(byte[] bytes) where T: struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T)); handle.Free(); return stuff; } 

Eu finalmente descobri uma maneira que não envolvia reflection e é mais amigável ao usuário. Ele usa a class DataConverter do Mono ( fonte ) que, infelizmente, é bastante problemática neste momento. (Por exemplo, floats e doubles parecem não funcionar corretamente, a análise de string é interrompida, etc.)

O truque é descompactar e reempacotar os bytes como big-endian, o que requer uma string descrevendo quais tipos estão na matriz de bytes (veja o último método) .Além disso, o alinhamento de bytes é complicado: existem quatro bytes na estrutura de um porque marshaling parece contar com alinhamento de 4 bytes (eu ainda não entendo bem essa parte). (EDIT: Eu descobri que adicionar Pack=1 ao atributo StructLayout geralmente cuida dos problemas de alinhamento de bytes.)

Observe que esse código de amostra foi usado no LINQPad – o método de extensão Dump apenas imprime informações sobre o object e retorna o object (é fluente).

 public void Main() { var beBytes = new byte[] { 0x80, 0x80, 0x80, 0x80, 0x80,0, 0x80,0, 0x80,0,0,0, 0x80,0,0,0, 0x80,0,0,0,0,0,0,0, 0x80,0,0,0,0,0,0,0, // 0,0,0x80,0x3F, // float of 1 // 0,0,0,0,0,0,0xF0,0x3F, // double of 1 0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0 }; var leBytes = new byte[] { 0x80, 0x80, 0x80, 0x80, 0,0x80, 0,0x80, 0,0,0,0x80, 0,0,0,0x80, 0,0,0,0,0,0,0,0x80, 0,0,0,0,0,0,0,0x80, // 0,0,0x80,0x3F, // float of 1 // 0,0,0,0,0,0,0xF0,0x3F, // double of 1 0x54,0x65,0x73,0x74,0x69,0x6E,0x67,0,0,0 // Testing\0\0\0 }; Foo fooLe = ByteArrayToStructure(leBytes).Dump("LE"); Foo fooBe = ByteArrayToStructureBigEndian(beBytes, "bbbbsSiIlL" // + "fd" // float, then double +"9bb").Dump("BE"); Assert.AreEqual(fooLe, fooBe); } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public byte b2; public byte b3; public byte b4; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; // public float f; // public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } T ByteArrayToStructure(byte[] bytes) where T: struct { GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T)); handle.Free(); return stuff; } T ByteArrayToStructureBigEndian(byte[] bytes, string description) where T: struct { byte[] buffer = bytes; IList unpacked = DataConverter.Unpack("^"+description, buffer, 0).Dump("unpacked"); buffer = DataConverter.PackEnumerable("!"+description, unpacked).Dump("packed"); return ByteArrayToStructure(buffer); } 

Eu concordo com o @weismat e acredito que não há solução.

O que você mostra em seu exemplo é que você pode acessar um buffer de byte bruto como se fosse uma estrutura OTHER sem alterar nada, não copiar ou mover dados, nada. Apenas prendê-lo para evitar que ele se mova por causa do GC.

Isso é basicamente o que você geralmente consegue em C usando um tipo de união que contém sua estrutura de destino e uma matriz de bytes do mesmo tamanho.

O lado bom é que é realmente eficiente.

Isso tem várias desvantagens, sendo a principal delas que você só pode acessar desta maneira os dados que estão na ordem de máquina nativa (seja LE ou BE). Portanto, seu ByteArrayToStructure não é realmente LE, é apenas porque o processador abaixo é LE. Se você compilar o mesmo programa em outro destino que seja BE, ele funcionará de outra maneira e acreditará que sua matriz de bytes é BE.

Outras desvantagens são que você deve ser muito cauteloso com alinhamento de dados, estar ciente de possíveis preenchimentos, etc. e é claro que não há como alterar a ordem de bytes de LE para BE sem mover dados em bytes (se você tiver 16 bits inteiros apenas matriz como em seu exemplo isso é simplesmente trocando a cada dois bytes).

Aconteceu que eu tenho um problema semelhante e não usei essa solução por causa das desvantagens anteriores e optei por ocultar minhas estruturas de input por trás de acessadores para ocultar o access à matriz de bytes abaixo. Pode não ser tão elegante, mas é simples e também evitar copiar o buffer ou mover dados de qualquer maneira.

Você já tentou MiscUtil? Tem uma class de utilitário chamada EndianBitConverter para converter entre matrizes de byte big e little endian.

Do meu ponto de vista, você só precisa adicionar um Array.Reverse () antes da conversão da matriz de bytes.

A solução tradicional é usar ntohl () e ntohs ().

 typedef struct { long foo; short bar, baz; char xyzzy; } Data; Data d; memcpy(&d, buffer, sizeof(Data)); d.foo = ntohl(d.foo); d.bar = ntohs(d.bar); d.baz = ntohs(d.baz); // don't need to change d.xyxxy 

O acima funciona em qualquer plataforma que tenha BSD Sockets, não importa se é big-endian, little-endian, ou algo totalmente estranho como um VAX. A operação inversa é feita usando hton * ().

Em plataformas big-endian, as funções geralmente não são operacionais e, portanto, devem ter custo zero.