C # StructLayout / FieldOffset e indexação em matrizes
Pergunta
Eu estou tendo um pouco de um problema usando FieldOffset corretamente com matrizes. O código abaixo é um exemplo onde ele não funciona corretamente para mim:
[StructLayout(LayoutKind.Explicit)]
public struct IndexStruct {
[FieldOffset(0)]
public byte[] data;
[FieldOffset(0)]
public short[] idx16;
[FieldOffset(0)]
public int[] idx32;
}
Se eu, por exemplo, define a matriz chamada "dados" para um array de bytes serializada e depois tentar recuperar dados como calções usando o campo "idx16" a indexação ainda está alinhada como um byte []. O que significa que idx16 1 obtém o segundo byte de dados, não o segundo 16bit palavra (byte 2 e 3). Se fazer o inverso calções índice i em vez de bytes, o que significa que o alinhamento do deslocamento é herdado a partir dos dados da fonte. A minha pergunta, existe uma maneira de contornar isso? Eu sei que eu posso compensar o valor do índice multiplicando com o tamanho do elemento, mas existe outra maneira?
Aqui é uma resposta que eu encontrei aqui no StackOverflow, mas ao tentar esse código descobriu-se que ele não estava funcionando corretamente. Tentou fazê-lo usando um teste de unidade no VS com o seguinte código sem qualquer sucesso:
[TestMethod()]
public void SumTest() {
float[] fArr = {2.0f, 0.5f, 0.0f, 1.0f};
MemoryStream ms = new MemoryStream();
for (int i = 0; i < fArr.Length; i++) {
ms.Write(BitConverter.GetBytes(fArr[i]), 0, sizeof(float));
}
byte[] buff = ms.ToArray();
double expected = 3.5f;
double actual = Sum(buff);
Assert.AreEqual(expected, actual);
}
Muito obrigado antecipadamente!
Solução
O problema é (pelo que posso ver) que você unioned referências das matrizes - assim qualquer variedade fica definido última vai ganhar. Uma vez que existe uma variedade, é usando o indexador (não byte offset) -. Modo que o tamanho não importa ??p>
A maneira de fazer isso "corretamente" (ou mal, conforme o caso pode ser) seria provavelmente com código inseguro - levando o ponteiro para a matriz - algo como:
IndexStruct s = new IndexStruct();
s.data = new byte[] { 1, 0, 0, 0, 1, 1 };
unsafe
{
fixed (short* data = s.idx16)
{
Console.WriteLine(data[0]); // should be 1 (little-endian)
Console.WriteLine(data[1]); // should be 0
Console.WriteLine(data[2]); // should be 257
}
}
É claro, eu não estou certo que eu recomendo - mas que parece conseguir o que deseja
Gostaria também de saber se você pode soltar a struct
completamente e usar apenas o acesso inseguro a um byte[]
diretamente:
byte[] raw = new byte[] { 1, 0, 0, 0, 1, 1 };
unsafe
{
fixed (byte* addr = raw)
{
short* s = (short*)addr;
Console.WriteLine(s[0]); // should be 1
Console.WriteLine(s[1]); // should be 0
Console.WriteLine(s[2]); // should be 257
}
}
Outras dicas
Seus define FieldOffset onde cada um dos seus elementos de dados estão dentro do struct ..
Ao definir-los todos para 0, você está dizendo ao compilador que todos eles na posição 0.
A segunda coisa que eu vejo é que você está criando uma matriz de bytes, shorts e ints.
ver: MSDN StructLayoutAttribute
[StructLayout(LayoutKind.Explicit)]
public struct IndexStruct {
[FieldOffset(0)]
public byte[16] data;
[FieldOffset(16)]
public short idx16;
[FieldOffset(18)]
public int idx32;
}