Представление битовых полей объединения с использованием StrucLayout и FieldOffset в c # - PullRequest
5 голосов
/ 28 января 2011

Я понимаю, что для представления объединений в C # мне нужно использовать атрибут StructLayout [LayoutKind.Explicit)] и [FieldOffset (x)], чтобы указать смещение байта внутри объединения.Однако у меня есть следующее объединение, которое я хочу представить, и атрибут FieldOffset смещается только на размер байта.

union _myUnion
{
     unsigned int info;
     struct
     {
          unsigned int flag1:1 // bit 0
          unsigned int flag2:1 // bit 1
          unsigned int flag3:1 // bit 2
          unsigned int flag4:1 // bit 3
          unsigned int flag5:1 // bit 4
          unsigned int flag6:1 // bit 5
          .
          .
          .
          unsigned int flag31:1 // bit 31
     }
}

Как вы можете видеть для внутренней структуры в объединении, я не могу использовать FieldOffset, так какМне нужно что-то, что может немного сместиться.

Есть ли решение для этого?Я пытаюсь вызвать функцию DLL, и одна из структур данных была определена как таковая, и у меня закончились идеи о том, как лучше всего представить эту структуру объединения.

Ответы [ 3 ]

4 голосов
/ 28 января 2011

Нет необходимости там объединяться; одно поле + свойство для данных, 8 свойств, которые выполняют побитовые операции «сдвига», например:

public uint Value {get;set;}

public uint Flag2 {
   get { return Value >> 2; }
}

и т.д.. Я бы тоже подумал, что ты хочешь здесь бул?

Обычно я бы сказал: не создавайте изменяемые структуры. PInvoke может (я не уверен) быть допустимым сценарием для этого, поэтому я его проигнорирую

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

1 голос
/ 24 ноября 2018

Самое элегантное решение для этого - использовать флаги enum

Лучше использовать uint вместо int

[Flags]
public enum MyEnum
    : uint
{
    None=0,
    Flag1=1,
    Flag2=1<<1,
    Flag3=1<<2,
    // etc
    Flag32=1<<31
}

После него вы можете использовать int как enum и как uint

MyEnum value=MyEnum.Flag1|MyEnum.Flag2;
uint uintValue=(uint)value;
// uintValue=3

Это будет обычно маршал от PInvoke

1 голос
/ 24 ноября 2018

Да, вы можете сделать это.Вы находитесь на правильном пути, ответ заключается в использовании BitVector32 вместе с атрибутами FieldOffset и StructLayout .Однако при этом необходимо помнить о нескольких вещах:

  1. Вам необходимо явно указать размер переменных, которые будут содержать данные.Этот первый элемент очень , на который важно обратить внимание.Например, в приведенном выше вопросе вы указываете info как unsigned int .Какой размер без знака int ?32 бита?64 бита?Это зависит от версии ОС, в которой работает данная конкретная версия .NET (это может быть .NET Core, Mono или Win32 / Win64).

  2. Что такое «порядковый номер» илибитовый порядок это?Опять же, мы можем работать на любом типе оборудования (например, Mobile / Xamarin, а не только на ноутбуке или планшете) - поэтому вы не можете принимать битовый порядок Intel.

  3. Нам понадобитсячтобы избежать любого управления памятью, которое зависит от языка, или в языке POD (обычные старые данные) на языке C / C ++.Это будет означать придерживаться только типов значений.

Я собираюсь сделать предположение, основанное на вашем вопросе и спецификации флагов 0-31, что sizeof (int) == 32 .

Хитрость заключается в том, чтобы обеспечить следующее:

  1. Все данные выровнены по байту.
  2. Поля битов и поле info выровнены ната же граница байта.

Вот как мы можем это сделать:

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

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

        // initialize bitfield sections
        flag1 = BitVector32.CreateSection(1);
        flag2 = BitVector32.CreateSection(1, flag1);
        flag3 = BitVector32.CreateSection(1, flag2);
    }

    #endregion

    #region Bifield

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

    #endregion

    #region Bitfield sections

    /// <summary>
    /// Section - Flag 1
    /// </summary>
    private static BitVector32.Section flag1;

    /// <summary>
    /// Section - Flag 2
    /// </summary>
    private static BitVector32.Section flag2;

    /// <summary>
    /// Section - Flag 3
    /// </summary>
    private static BitVector32.Section flag3;

    #endregion

    #region Properties

    /// <summary>
    /// Flag 1
    /// </summary>
    public bool Flag1
    {
        get { return info[flag1] != 0; }
        set { info[flag1] = value ? 1 : 0; }
    }

    /// <summary>
    /// Flag 2
    /// </summary>
    public bool Flag2
    {
        get { return info[flag2] != 0; }
        set { info[flag2] = value ? 1 : 0; }
    }

    /// <summary>
    /// Flag 1
    /// </summary>
    public bool Flag3
    {
        get { return info[flag3] != 0; }
        set { info[flag3] = value ? 1 : 0; }
    }

    #endregion

    #region ToString

    /// <summary>
    /// Allows us to represent this in human readable form
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return $"Name: {nameof(MyUnion)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3}  {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
    }

    #endregion
}

Обратите особое внимание на конструктор.По определению, вы не можете определить конструктор по умолчанию для структур в C #.Однако нам нужен какой-то способ обеспечить правильную инициализацию объекта BitVector32 и его разделов перед использованием.Мы достигаем этого, требуя конструктор, который принимает фиктивный целочисленный параметр, и инициализируем объект следующим образом:

    /// <summary>
    /// Main entry point
    /// </summary>
    /// <param name="args"></param>
    static void Main(string[] args)
    {
        // brew up one of these...
        var myUnion = new MyUnion(0)
        {
            Flag2 = true
        };

Кстати, вы никоим образом не ограничены отдельными битовыми полями - вы можете определитьлюбой размер битового поля, который вам нравится.Например, если бы я изменил ваш пример на:

union _myUnion
{
    unsigned int info;
    struct
    {
        unsigned int flag1 : 3 // bit 0-2
        unsigned int flag2 : 1 // bit 3
        unsigned int flag3 : 4 // bit 4-7
            .
            .
            .
        unsigned int flag31 : 1 // bit 31
    }
}

Я бы просто изменил свой класс на:

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

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

        // initialize bitfield sections
        flag1 = BitVector32.CreateSection(0x07);
        flag2 = BitVector32.CreateSection(1, flag1);
        flag3 = BitVector32.CreateSection(0x0f, flag2);
    }

    #endregion

    #region Bifield

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

    #endregion

    #region Bitfield sections

    /// <summary>
    /// Section - Flag1
    /// </summary>
    private static BitVector32.Section flag1;

    /// <summary>
    /// Section - Flag2
    /// </summary>
    private static BitVector32.Section flag2;

    /// <summary>
    /// Section - Flag3
    /// </summary>
    private static BitVector32.Section flag3;

    #endregion

    #region Properties

    /// <summary>
    /// Flag 1
    /// </summary>
    public int Flag1
    {
        get { return info[flag1]; }
        set { info[flag1] = value; }
    }

    /// <summary>
    /// Flag 2
    /// </summary>
    public bool Flag2
    {
        get { return info[flag2] != 0; }
        set { info[flag2] = value ? 1 : 0; }
    }

    /// <summary>
    /// Flag 1
    /// </summary>
    public int Flag3
    {
        get { return info[flag3]; }
        set { info[flag3] = value; }
    }

    #endregion

    #region ToString

    /// <summary>
    /// Allows us to represent this in human readable form
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return $"Name: {nameof(MyUnion2)}{Environment.NewLine}Flag1: {Flag1}: Flag2: {Flag2} Flag3: {Flag3}  {Environment.NewLine}BitVector32: {info}{Environment.NewLine}";
    }

    #endregion
}

Последнее слово об этой теме ... этодолжно быть очевидно, что это должно быть сделано только в том случае, если вы абсолютно ДОЛЖНЫ сделать это.Очевидно, что для этого требуются специальные знания вашей операционной системы, языка, на котором вы работаете, вашего соглашения о вызовах и множества других хрупких требований.

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

Caveat emptor!

...