如何在 C# 中静态模拟 C 位域?

How to emulate statically the C bitfields in c#?

我必须定义一个通信协议,我想使用一个位域来存储一些逻辑值。

我在两个系统上工作:发送方:一个设备和一个 .Net 软件作为接收方。

在固件方面,我通常将位域结构定义为:

struct __attribute__((__packed__)) BitsField 
{
  // Logic values
  uint8_t vesselPresenceSw: 1;
  uint8_t drawerPresenceSw: 1;
  uint8_t pumpState: 1;
  uint8_t waterValveState: 1;
  uint8_t steamValveState: 1;
  uint8_t motorDriverState: 1;
    // Unused
  uint8_t unused_0: 1;
  uint8_t unused_1: 1;
};

如何在支持字节反序列化的软件端定义相同的结构来构建结构本身?

请看例子,

C代码,

struct example_bit_field
{
unsigned char bit1 : 1;
unsigned char bit2 : 1;
unsigned char two_bits : 2;
unsigned char four_bits : 4;
}

和 C# 等效,

[BitFieldNumberOfBitsAttribute(8)]
struct ExampleBitField : IBitField
{
[BitFieldInfo(0, 1)]
public bool Bit1 { get; set; }
[BitFieldInfo(1, 1)]
public byte Bit2 { get; set; }
[BitFieldInfo(2, 2)]
public byte TwoBits { get; set; }
[BitFieldInfo(4, 4)]
public byte FourBits { get; set; }
}

来源:- https://www.codeproject.com/Articles/1095576/Bit-Field-in-Csharp-using-struct

恐怕没有与 C 样式位域结构等效的直接 C#。

C# 能够在一定程度上通过使用 FieldOffset 属性来近似 C 风格的 unions。这些明确的布局属性允许您指定精确的和可能重叠的字段偏移量。不幸的是,这甚至不能让你做到一半:必须以 字节 而不是位来指定偏移量,并且在读取或写入重叠字段时不能强制执行特定宽度。

最接近原生支持位域的 C# 可能是基于标志的 enum 类型。如果您不需要超过 64 位,您可能会发现这已经足够了。首先根据适合所有标志的最小无符号类型声明 enum

[Flags]
public enum BitFields : byte {
    None             =      0,
    VesselPresenceSw = 1 << 0,
    DrawerPresenceSw = 1 << 1,
    PumpState        = 1 << 2,
    WaterValveState  = 1 << 3,
    SteamValveState  = 1 << 4,
    MotorDriverState = 1 << 5
}

命名的项目可以分配给它们适合基础类型的任何值(在本例中为 byte),因此如果您愿意,一个项目可以代表多个位。请注意,如果您想直接与 C 风格的位域互操作,您的第一个值应该从 most significant 位开始,而不是 least.

要使用您的标志,只需声明一个新类型的变量或字段并执行您需要的任何按位运算:

BitFields bits = BitFields.None;

bits |= BitFields.VesselPresenceSw | BitFields.PumpState;
bits &= ~BitFields.VesselPresenceSw;

// etc.

从好的方面来说,用 [Flags] 声明的枚举在调试器中显示或转换为字符串时格式很好。例如,如果您要打印表达式 BitFields.VesselPresenceSw | BitFields.PumpState,您将得到文本 DrawerPresenceSw, PumpState.

有一个警告:enum 的存储将接受适合基础类型的任何值。这样写是完全合法的:

BitFields badBits = (BitFields)0xFF;

这设置了 byte 大小的枚举的所有 8 位,但我们的命名值仅涵盖 6 位。根据您的要求,您可能希望声明一个仅包含 'legal' 标志的常量,您可以 & 反对它。

如果您需要比这更丰富的内容,可以使用名为 BitArray 的框架级 'bitfield' 数据结构。但是,BitArray 是一种引用类型,它使用托管的 int[] 进行存储。如果你想要一个可以用于互操作目的或任何类型的内存映射的 struct,它不会帮助你。

你可以试试模仿这样的struct。看来,您想在 interop 中使用它(例如,C 例程与 C# 程序交换数据)。由于您有 logic 值,因此将它们公开为 bool:

  using System.Runtime.InteropServices;

  ...

  [StructLayout(LayoutKind.Sequential, Pack = 1)]
  public struct MyBitsField {
    private Byte m_Data; // We actually store Byte

    public MyBitsField(Byte data) {
      m_Data = data;
    }

    private bool GetBit(int index) {
      return (m_Data & (1 << index)) != 0;
    }

    private void SetBit(int index, bool value) {
      byte v = (byte)(1 << index);

      if (value)
        m_Data |= v;
      else
        m_Data = (byte) ((m_Data | v) ^ v);
    }

    public bool vesselPresenceSw {
      get { return GetBit(0); }
      set { SetBit(0, value); }
    }

    ...

    public bool motorDriverState {
      get { return GetBit(5); }
      set { SetBit(5, value); }
    }
  }

用法:

  var itemToSend = new MyBitsField() {
    vesselPresenceSw = false,
    motorDriverState = true,
  };

同时,我也有类似的想法@Dmitry。 我使用 FieldOffset 属性找到了以下解决方案。 无需额外代码即可正常运行。我觉得可以接受。

[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct LiveDataBitField
{
    // Where the values are effectively stored
    public byte WholeField { get; private set; }

    public bool VesselPresenceSw
    {
        get => (WholeField & 0x1) > 0;
        set
        {
            if (value)
            {
                WholeField |= 1;
            }
            else
            {
                WholeField &= 0xfE;
            }
        }
    }

    public bool DrawerPresenceSw
    {
        get => (WholeField & 0x2) >> 1 > 0;
        set
        {
            if (value)
            {
                WholeField |= (1 << 1);
            }
            else
            {
                WholeField &= 0xFD;
            }
        }
    }
    public bool PumpState
    {
        get => (WholeField & 0x4) >> 2 > 0;
        set
        {
            if (value)
            {
                WholeField |= (1 << 2);
            }
            else
            {
                WholeField &= 0xFB;
            }
        }
    }
    public bool WaterValveState
    {
        get => (WholeField & 0x8) >> 3 > 0;
        set
        {
            if (value)
            {
                WholeField |= (1 << 3);
            }
            else
            {
                WholeField &= 0xF7;
            }
        }
    }
    public bool SteamValveState
    {
        get => (WholeField & 0x10) >> 4 > 0;
        set
        {
            if (value)
            {
                WholeField |= (1 << 4);
            }
            else
            {
                WholeField &= 0xEF;
            }
        }
    }
    public bool MotorDriverState
    {
        get => (WholeField & 0x20) >> 5 > 0;
        set
        {
            if (value)
            {
                WholeField |= (1 << 5);
            }
            else
            {
                WholeField &= 0xDF;
            }
        }
    }
}

要将字节数组反序列化为结构,您可以使用:

    public static object ReadStruct(byte[] data, Type type)
    {
        var pinnedPacket = GCHandle.Alloc(data, GCHandleType.Pinned);
        var obj = Marshal.PtrToStructure(pinnedPacket.AddrOfPinnedObject(), type); 
        pinnedPacket.Free();
        return obj;
    }