Представление битовых полей объединения с использованием StrucLayout и FieldOffset в C#
Я понимаю, что для представления объединений в 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 ответа
Нет необходимости в союзе там; одно поле + свойство для данных, 8 свойств, которые выполняют побитовые операции "сдвига", например:
public uint Value {get;set;}
public uint Flag2 {
get { return Value >> 2; }
}
и т. д. Я бы тоже подумал, что ты хочешь здесь бул?
Обычно я бы сказал: не создавайте изменяемые структуры. PInvoke может (я не уверен) быть подходящим сценарием для этого, поэтому я его проигнорирую:)
Если значение действительно использует более 32 бит, рассмотрите возможность переключения вспомогательного поля на ulong.
Да, ты можешь это сделать. Вы находитесь на правильном пути, ответ заключается в использовании BitVector32 вместе с атрибутами FieldOffset и StructLayout. Однако при этом необходимо помнить о нескольких вещах:
Вам необходимо явно указать размер переменных, которые будут содержать данные. На этот первый пункт очень важно обратить внимание. Например, в приведенном выше вопросе вы указываете информацию как unsigned int. Какой размер без знака int? 32 бита? 64 бита? Это зависит от версии ОС, в которой работает данная конкретная версия.NET (это может быть.NET Core, Mono или Win32/Win64).
Что это за порядок байтов или порядок следования битов? Опять же, мы можем работать на любом типе оборудования (например, Mobile/Xamarin, а не только на ноутбуке или планшете) - поэтому вы не можете предполагать порядок следования битов Intel.
Мы хотим избежать любого управления памятью, которое зависит от языка, или в языке POD (обычные старые данные) на языке C/C++. Это будет означать придерживаться только типов значений.
Я собираюсь сделать предположение, основанное на вашем вопросе и спецификации флагов 0-31, что sizeof (int) == 32.
Хитрость заключается в том, чтобы обеспечить следующее:
- Все данные выровнены по байту.
- Битовые поля и информационное поле выравниваются на одной границе байта.
Вот как мы это делаем:
[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
}
Последнее слово об этой теме... должно быть очевидно, что это должно быть сделано только в том случае, если вы абсолютно ДОЛЖНЫ сделать это. Понятно, что для этого требуются специальные знания вашей среды ОС, языка, на котором вы работаете, соглашения о вызовах и множества других хрупких требований.
В любом другом контексте здесь так много запахов кода, что это явно кричит о непереносимости. Но из контекста вашего вопроса я бы предположил, что все дело в том, что вам нужно работать близко к аппаратному обеспечению и нуждаться в такой точности.
Пусть покупатель будет бдителен!
Наиболее элегантным решением для этого является использование флагов 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