Распределение записей с битовыми полями в C#

Возможно ли маршалировать структуру в стиле C, содержащую битовые поля, в структуру C#, или вам придется маршалировать ее в базовый тип, а затем делать битовые маски?

Например, я хотел бы выделить из структуры стиля C, как это:

struct rgb16 {
    unsigned int R : 4;
    unsigned int G : 5;
    unsigned int B : 4;
}

И направить это на что-то вроде этого:

[StructLayout(LayoutKind.Sequential)]
public struct Rgb16 {
    public byte R;
    public byte G;
    public byte B;
}

4 ответа

Решение

В C# нет битовых полей. Таким образом, я бы пошел со свойствами, которые инкапсулируют биты:

[StructLayout(LayoutKind.Sequential)]
public struct Rgb16 {
    private readonly UInt16 raw;
    public byte R{get{return (byte)((raw>>0)&0x1F);}}
    public byte G{get{return (byte)((raw>>5)&0x3F);}}
    public byte B{get{return (byte)((raw>>11)&0x1F);}}

    public Rgb16(byte r, byte g, byte b)
    {
      Contract.Requires(r<0x20);
      Contract.Requires(g<0x40);
      Contract.Requires(b<0x20);
      raw=r|g<<5|b<<11;
    }
}

Я избегал добавления сеттеров, потому что мне нравятся неизменяемые структуры, но в принципе вы тоже можете их добавлять.

Это мой "безопасный C#" порт структуры rgb16.

[StructLayout(LayoutKind.Explicit, Size = 2, Pack = 1)]
public class Color16
{
    // Btifield: 5
    [FieldOffset(0)]
    private ushort b_i;

    public ushort b
    {
        get { return (ushort)((b_i >> 11) & 0x1F); }
        set { b_i = (ushort)((b_i & ~(0x1F << 11)) | (value & 0x3F) << 11); }
    }

    // Bitfield: 6
    [FieldOffset(0)]
    private ushort g_i;

    public ushort g
    {
        get { return (ushort)((g_i >> 5) & 0x7F); }
        set { g_i = (ushort)((g_i & ~(0x7F << 5)) | (value & 0x7F) << 5); }
    }

    // Bitfield: 5
    [FieldOffset(0)]
    private ushort r_i;

    public ushort r
    {
        get { return (ushort) (r_i & 0x1F); }
        set { r_i = (ushort) ((r_i & ~0x1F) | (value & 0x1F)); }
    }

    [FieldOffset(0)]
    public ushort u;

    public Color16() { }
    public Color16(Color16 c) { u = c.u; }
    public Color16(ushort U) { u = U; }

}

Большую часть вчерашнего дня я потратил, пытаясь решить эту проблему, как большую часть проблемы "Представления битовых полей объединения с использованием StrucLayout и FieldOffset в C#", которая не только ответит на ваш вопрос (см. Выше), но и может быть найдена здесь:

Представление битовых полей объединения с использованием StrucLayout и FieldOffset в C#

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

Просто для удовольствия, я подумал, что я хотел бы создать структуру C# для вашего примера RGB16:

Примечание: объект BitVector32 имеет длину 32 бита, поэтому "16" на прозвище вводит в заблуждение... обратите внимание на это

[StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
public struct Rgb16
{
    #region Lifetime

    /// <summary>
    /// Ctor
    /// </summary>
    /// <param name="foo"></param>
    public Rgb16(int foo)
    {
        // allocate the bitfield
        buffer = new BitVector32(0);

        // initialize bitfield sections
        r = BitVector32.CreateSection(0x0f);        // 4
        g = BitVector32.CreateSection(0x1f, r);     // 5
        b = BitVector32.CreateSection(0x0f, g);     // 4
    }

    #endregion

    #region Bifield

    // Creates and initializes a BitVector32.
    [FieldOffset(0)]
    private BitVector32 buffer;

    #endregion

    #region Bitfield sections

    /// <summary>
    /// Section - Red
    /// </summary>
    private static BitVector32.Section r;

    /// <summary>
    /// Section - Green
    /// </summary>
    private static BitVector32.Section g;

    /// <summary>
    /// Section - Blue
    /// </summary>
    private static BitVector32.Section b;

    #endregion

    #region Properties

    /// <summary>
    /// Flag 1
    /// </summary>
    public byte R
    {
        get { return (byte)buffer[r]; }
        set { buffer[r] = value; }
    }

    /// <summary>
    /// Flag 2
    /// </summary>
    public byte G
    {
        get { return (byte)buffer[g]; }
        set { buffer[g] = value; }
    }

    /// <summary>
    /// Flag 1
    /// </summary>
    public byte B
    {
        get { return (byte)buffer[b]; }
        set { buffer[b] = value; }
    }

    #endregion

    #region ToString

    /// <summary>
    /// Allows us to represent this in human readable form
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return $"Name: {nameof(Rgb16)}{Environment.NewLine}Red: {R}: Green: {G} Blue: {B}  {Environment.NewLine}BitVector32: {buffer}{Environment.NewLine}";
    }

    #endregion
}

Чтобы использовать это, вы должны выделить следующее:

internal static class Program
{
    /// <summary>
    /// Main entry point
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {

        var rgb16 = new Rgb16(0)
        {
            R = 24,
            G = 16,
            B = 42
        };

Также обратите внимание, что есть ссылка на это:

Битовые поля в C#

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

  1. Обязательно упакуйте свои данные на границе байта
  2. Убедитесь, что вы указали размер ваших типов данных, т.е. int меняет размер в зависимости от аппаратного обеспечения, System.Int32 - нет.
  3. Убедитесь, что вы соблюдаете "порядковый номер" ваших целочисленных типов данных
  4. Избегайте, если это вообще возможно, каких-либо связей с базовым языком, т.е. избегайте менеджера языковой памяти - придерживайтесь "простых старых типов данных". Это значительно упростит маршалинг данных по сети.

Я упорядочил битовые поля, такие как:

public struct Rgb16 {
    public ushort Value; // two byte value that internally contain structure R(4):G(5):B(4)

    public Rgb16BitField GetBitField
    {
        get; set;
    }
}

где свойство создает новую структуру, как вы упомянули, путем деления значения на биты.

Это не лучший способ сделать это, но я не нашел ничего другого, что сработало бы для меня. Я могу предоставить код для GetBitField, если вы хотите (он не очень компактен)

Upd: Ссылка, предоставленная Тони в комментариях к вашему вопросу, использует ту же идею, но кажется более точной, чем моя, поэтому используйте его решение, если вы не можете найти лучшего

Другие вопросы по тегам