【问题标题】:Marshalling stucts with bit-fields in C#在 C# 中使用位域编组结构
【发布时间】:2012-02-16 12:44:24
【问题描述】:

是否可以将包含位字段的 C 样式结构编组为 C# 结构,还是必须将其编组为基本类型,然后进行位掩码?

例如我想从这样的 C 风格结构中编组:

struct rgb16 {
    unsigned int R : 4;
    unsigned int G : 5;
    unsigned int B : 4;
}

然后将其编组成这样的东西:

[StructLayout(LayoutKind.Sequential)]
public struct Rgb16 {
    public byte R;
    public byte G;
    public byte B;
}

【问题讨论】:

标签: c# marshalling bit-fields


【解决方案1】:

C# 中没有位域。所以我会选择封装一些摆弄的属性:

[StructLayout(LayoutKind.Sequential)]
public struct Rgb16 {
    private readonly UInt16 raw;
    public byte R{get{return (byte)((raw>>0)&0x1F);}}
    public byte G{get{return (byte)((raw>>5)&0x3F);}}
    public byte B{get{return (byte)((raw>>11)&0x1F);}}

    public Rgb16(byte r, byte g, byte b)
    {
      Contract.Requires(r<0x20);
      Contract.Requires(g<0x40);
      Contract.Requires(b<0x20);
      raw=r|g<<5|b<<11;
    }
}

我避免添加 setter,因为我喜欢不可变结构,但原则上您也可以添加它们。

【讨论】:

  • 到目前为止我的表现差不多。我希望我错过了什么,但我猜 c'est la vie
【解决方案2】:

这是我的 rgb16 结构的“安全 c#”端口。

[StructLayout(LayoutKind.Explicit, Size = 2, Pack = 1)]
public class Color16
{
    // Btifield: 5
    [FieldOffset(0)]
    private ushort b_i;

    public ushort b
    {
        get { return (ushort)((b_i >> 11) & 0x1F); }
        set { b_i = (ushort)((b_i & ~(0x1F << 11)) | (value & 0x3F) << 11); }
    }

    // Bitfield: 6
    [FieldOffset(0)]
    private ushort g_i;

    public ushort g
    {
        get { return (ushort)((g_i >> 5) & 0x7F); }
        set { g_i = (ushort)((g_i & ~(0x7F << 5)) | (value & 0x7F) << 5); }
    }

    // Bitfield: 5
    [FieldOffset(0)]
    private ushort r_i;

    public ushort r
    {
        get { return (ushort) (r_i & 0x1F); }
        set { r_i = (ushort) ((r_i & ~0x1F) | (value & 0x1F)); }
    }

    [FieldOffset(0)]
    public ushort u;

    public Color16() { }
    public Color16(Color16 c) { u = c.u; }
    public Color16(ushort U) { u = U; }

}

【讨论】:

    【解决方案3】:

    我昨天花了大部分时间试图解决这个问题,作为“使用 c# 的 StrucLayout 和 FieldOffset 表示联合位域”问题的一个更大的部分,这不仅会回答你的问题(上图),而且可以在这里找到:

    Representing union bitfields using c#'s StrucLayout and FieldOffset

    本质上,您需要定义一个结构(值类型)并使用 BitVector32 对象为您希望表示的每个位域定义位域部分。您可以跳过关于工会的部分,因为这与您的问题无关,但大部分帖子仍然与您的问题有关。

    只是为了好玩,我想我会为您的 RGB16 示例创建 C# 结构:

    注意:BitVector32 对象的长度为 32 位,因此名称上的“16”有点误导...请注意这一点

    [StructLayout(LayoutKind.Explicit, Size = 1, CharSet = CharSet.Ansi)]
    public struct Rgb16
    {
        #region Lifetime
    
        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="foo"></param>
        public Rgb16(int foo)
        {
            // allocate the bitfield
            buffer = new BitVector32(0);
    
            // initialize bitfield sections
            r = BitVector32.CreateSection(0x0f);        // 4
            g = BitVector32.CreateSection(0x1f, r);     // 5
            b = BitVector32.CreateSection(0x0f, g);     // 4
        }
    
        #endregion
    
        #region Bifield
    
        // Creates and initializes a BitVector32.
        [FieldOffset(0)]
        private BitVector32 buffer;
    
        #endregion
    
        #region Bitfield sections
    
        /// <summary>
        /// Section - Red
        /// </summary>
        private static BitVector32.Section r;
    
        /// <summary>
        /// Section - Green
        /// </summary>
        private static BitVector32.Section g;
    
        /// <summary>
        /// Section - Blue
        /// </summary>
        private static BitVector32.Section b;
    
        #endregion
    
        #region Properties
    
        /// <summary>
        /// Flag 1
        /// </summary>
        public byte R
        {
            get { return (byte)buffer[r]; }
            set { buffer[r] = value; }
        }
    
        /// <summary>
        /// Flag 2
        /// </summary>
        public byte G
        {
            get { return (byte)buffer[g]; }
            set { buffer[g] = value; }
        }
    
        /// <summary>
        /// Flag 1
        /// </summary>
        public byte B
        {
            get { return (byte)buffer[b]; }
            set { buffer[b] = value; }
        }
    
        #endregion
    
        #region ToString
    
        /// <summary>
        /// Allows us to represent this in human readable form
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return $"Name: {nameof(Rgb16)}{Environment.NewLine}Red: {R}: Green: {G} Blue: {B}  {Environment.NewLine}BitVector32: {buffer}{Environment.NewLine}";
        }
    
        #endregion
    }
    

    要使用它,您将分配如下:

    internal static class Program
    {
        /// <summary>
        /// Main entry point
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
    
            var rgb16 = new Rgb16(0)
            {
                R = 24,
                G = 16,
                B = 42
            };
    

    另外,请注意这里有一个参考:

    Bit fields in C#

    这里还有许多其他答案,但它们有许多需要注意的陷阱。也许我在这里能做的最好的事情就是列出您可能想要查找的内容:

    1. 确保将数据打包在字节边界上
    2. 确保指定数据类型的大小,即 int 会根据硬件改变大小,System.Int32 不会。
    3. 确保遵守整数数据类型的“字节顺序”
    4. 尽可能避免与底层语言有任何联系,即避免使用语言内存管理器——坚持“普通旧数据类型”。这将使通过网络传输数据变得更加简单。

    【讨论】:

      【解决方案4】:

      我已经编组了位域,例如:

      public struct Rgb16 {
          public ushort Value; // two byte value that internally contain structure R(4):G(5):B(4)
      
          public Rgb16BitField GetBitField
          {
              get; set;
          }
      }
      

      该属性通过将 Value 划分为位来创建新结构,就像您提到的那样。

      这不是最好的方法,但还没有找到对我有用的其他方法。 如果你愿意,我可以提供 GetBitField 的代码(它不是很紧凑)

      更新: Tony 在 cmets 中为您的问题提供的链接使用了相同的想法,但似乎比我的更准确,因此如果您找不到更好的解决方案,请使用他的解决方案

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-01
        • 2015-12-05
        • 1970-01-01
        相关资源
        最近更新 更多