Um Guia Abrangente para Ler Estruturas de Dados C/C++ em C# de um Array byte[]
Ao migrar ou trabalhar com estruturas de dados entre linguagens, especialmente do C/C++ para o C#, os desenvolvedores frequentemente enfrentam o desafio de traduzir a representação em bytes de uma struct C/C++ em um equivalente gerenciável em C#. Este post no blog aborda como converter eficientemente um array byte[]
contendo dados de structs C/C++ em uma struct C#, sem a desordem que frequentemente decorre da cópia desnecessária de dados.
Compreendendo o Problema
Considere a seguinte definição de struct em C:
typedef struct OldStuff {
CHAR Name[8];
UInt32 User;
CHAR Location[8];
UInt32 TimeStamp;
UInt32 Sequence;
CHAR Tracking[16];
CHAR Filler[12];
} OldStuff;
Essa representação consiste em uma sequência fixa de caracteres e inteiros. Ao trabalhar com esse tipo de dados binários em C#, os desenvolvedores enfrentam o desafio de alinhar a estrutura corretamente e convertê-la para uso em código gerenciado.
A struct correspondente em C# utiliza StructLayout
para controlar como os dados são organizados na memória:
[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;
}
A Abordagem Convencional
Uma abordagem típica que os desenvolvedores adotam envolve copiar o array de bytes para um buffer separado antes de tentar marshallá-lo na struct C# desejada. Aqui está um método frequentemente utilizado:
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();
Embora essa abordagem funcione, ela introduz sobrecarga desnecessária, especialmente em relação ao uso de memória e desempenho, ao criar uma cópia intermediária dos dados.
Uma Solução Melhor
Em vez de duplicar os dados, você pode utilizar diretamente o array de bytes para extrair sua struct. Aqui está um método mais eficiente:
Usando Diretamente 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();
}
}
Este método aloca um handle para o array de bytes original e elimina a necessidade de criar um buffer adicional.
Método Genérico para Flexibilidade
Se você deseja converter qualquer struct de um array de bytes, você pode criar um método genérico:
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;
}
Abordagem Unsafe para Melhor Desempenho
Para usuários avançados, utilizar o contexto unsafe
pode gerar um desempenho ainda melhor, pois permite que você trabalhe diretamente com ponteiros:
unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
fixed (byte* ptr = &bytes[0])
{
return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
}
}
Conclusão
Ler efetivamente uma estrutura de dados C/C++ em C# a partir de um array byte[]
não precisa ser complicado. Ao empregar o GCHandle
diretamente no array de bytes ou usando código unsafe
, você melhora o desempenho e simplifica seu código. Adotar essas abordagens resulta em um código mais limpo e de fácil manutenção, evitando sobrecarga desnecessária de memória.
Portanto, da próxima vez que você se deparar com a conversão de dados entre linguagens, lembre-se desses métodos para simplificar seu processo!