【问题标题】:How to emulate statically the C bitfields in c#?如何在 C# 中静态模拟 C 位域?
【发布时间】:2017-09-15 10:59:28
【问题描述】:

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

我在两个系统上工作:发送者:一个设备和一个 .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# bit-fields


    【解决方案1】:

    恐怕没有直接的 C# 等效于 C 风格的位域结构。

    C# 能够在有限的范围内通过使用FieldOffset 属性来近似C 风格的联合。这些显式布局属性允许您指定准确且可能重叠的字段偏移量。不幸的是,这甚至不能让你走到一半:偏移量必须以 bytes 而不是位指定,并且在读取或写入重叠字段时不能强制指定特定宽度。

    最接近原生支持位域的 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 样式的位域进行互操作,您的第一个值应该从 最重要的位开始,而不是从最不重要的位开始。

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

    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 位。根据您的要求,您可能希望声明一个仅包含“合法”标志的常量,您可以 &amp; 反对。

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

    【讨论】:

    • 只是想评论一下使用 [Flags] 枚举的可能性,你正在更新你的答案:) 我认为这是解决这个问题的最好和最简单的方法
    • @pkuderov 同意!我有点尴尬,我花了这么长时间才想到它:)。
    【解决方案2】:

    请看一个例​​子,

    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

    【讨论】:

    • 是的,但我需要创建一个属性!我想知道有一个简短的解决方案
    • 我不认为你可以摆脱属性
    【解决方案3】:

    你可以尝试模仿这样的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,
      };
    

    【讨论】:

      【解决方案4】:

      与此同时,@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;
          }
      

      【讨论】:

      • 您将所有字段置于相同的偏移量。这实际上与使用单个字段相同,那么为什么不只使用单个字段呢?此外,您的 set 逻辑是错误的。尝试将一个设置为true,然后返回false,看看会发生什么。
      • 谢谢,@MikeStrobel,我修好了。
      猜你喜欢
      • 2019-09-26
      • 2010-09-07
      • 1970-01-01
      • 2012-09-16
      • 2011-09-23
      • 2011-05-02
      • 2012-08-31
      • 2010-12-23
      • 2013-12-25
      相关资源
      最近更新 更多