C # StructLayout / FieldOffset и индексация в массивах - PullRequest
4 голосов
/ 08 мая 2009

У меня небольшая проблема с использованием FieldOffset правильно с массивами. Приведенный ниже код является примером, где он не работает правильно для меня:

[StructLayout(LayoutKind.Explicit)]
public struct IndexStruct {
    [FieldOffset(0)]
    public byte[] data;

    [FieldOffset(0)]
    public short[] idx16;

    [FieldOffset(0)]
    public int[] idx32;
}

Если, например, я устанавливаю массив с именем «data» в сериализованный байтовый массив, а затем пытаюсь извлечь данные в виде шорт, используя поле «idx16», индексирование все еще выравнивается как байт []. Это означает, что idx16 1 извлекает второй байт данных, а не второе 16-битное слово (байт 2 и 3). Если я делаю обратные индексные шорты вместо байтов, это означает, что выравнивание смещения наследуется от исходных данных. Мой вопрос, есть ли способ обойти это? Я знаю, что могу компенсировать значение индекса путем умножения на размер элемента, но есть ли другой способ?

Здесь - это ответ, который я нашел здесь в StackOverflow, но при попытке этого кода оказалось, что он не работает должным образом. Попробовал, используя модульный тест в VS со следующим кодом, но безуспешно:

[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);
}

Большое спасибо заранее!

Ответы [ 2 ]

6 голосов
/ 08 мая 2009

Проблема (из того, что я вижу) в том, что вы объединили ссылки массивов - так, какой массив будет установлен последним, победит. Когда есть массив, он использует индексатор (а не смещение в байтах) - поэтому размер не имеет значения.

Способ сделать это "должным образом" (или неправильно, в зависимости от обстоятельств), вероятно, будет с помощью небезопасного кода - с указателем на массив - что-то вроде:

    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
        }
    }

Конечно, я не уверен, что рекомендую это - но это, кажется, достигает того, чего вы хотите?

Мне также интересно, можете ли вы полностью сбросить struct и просто использовать небезопасный доступ к byte[] напрямую:

    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
        }
    }
0 голосов
/ 08 мая 2009

Ваш FieldOffset определяет, где каждый из ваших элементов данных находится внутри структуры.

Устанавливая их все в 0, вы сообщаете компилятору, что они все в позиции 0.

Второе, что я вижу, это то, что вы создаете массив байтов, шортов и целых чисел.

см .: MSDN StructLayoutAttribute

[StructLayout(LayoutKind.Explicit)]
public struct IndexStruct {
        [FieldOffset(0)]
        public byte[16] data;

        [FieldOffset(16)]
        public short idx16;

        [FieldOffset(18)]
        public int idx32;
}
...