使用c#的StrucLayout和FieldOffset表示联合位域

时间:2011-01-28 17:05:37

标签: c# union bit-fields

据我所知,为了在C#中表示联合,我需要使用StructLayout [LayoutKind.Explicit]]和[FieldOffset(x)] attribut来指定联合内部的字节偏移量。但是,我有一个我想表示的跟随联合,而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 个答案:

答案 0 :(得分:4)

不需要在那里结合;一个字段+数据属性,8个按位“移位”操作的属性,例如:

public uint Value {get;set;}

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

等。我还以为你想要布尔这里?

通常我会说:不要制作可变的结构。 PInvoke 可能(我不确定)是一个有效的场景,所以我会忽略它:)

如果值真的使用超过32位,请考虑将支持字段切换为ulong。

答案 1 :(得分:1)

是的,您可以这样做。您在正确的道路上,答案在于使用 BitVector32 以及 FieldOffset StructLayout 属性。但是,在执行此操作时,需要记住许多事项:

  1. 您需要明确指定将包含相关数据的变量的大小。第一项非常重要,要注意。例如,在上述问题中,您将 info 指定为 unsigned int unsigned int 是什么大小? 32位? 64位?这取决于运行特定版本.NET的操作系统版本(可能是.NET Core,Mono或Win32 / Win64)。

  2. 这是什么“字节序”或位顺序?同样,我们可能正在任何类型的硬件上运行(想想Mobile / Xamarin,而不仅仅是笔记本电脑或平板电脑),因此,您不能假设Intel的位顺序。

  3. 我们将要避免依赖于语言的任何内存管理,或者避免使用C / C ++用语POD(普通旧数据)类型。那将意味着只坚持值类型。

根据您的问题和标志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
}

关于此主题的最后一句话...很明显,只有在您绝对必须这样做时,才应这样做。显然,这需要您对OS环境,运行语言,调用约定以及许多其他 brittle 要求有专门的知识。

在任何其他情况下,这里都有太多的代码味道,以至于明显地发出了不可移植的声音。但是从您的问题的角度来看,我想得出的全部结论是,您需要靠近硬件运行并且需要这种精度。

偷闲者!

答案 2 :(得分:1)

最优雅的解决方案是使用标志枚举

最好用uint代替int

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

之后,您可以将int用作枚举和uint

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

通常由PInvoke封送