Bit fields in C#

匿名 (未验证) 提交于 2019-12-03 08:28:06

问题:

I have a structure which I need to populate and write to disk (several actually).

An example is:

byte-6     bit0 - original_or_copy   bit1 - copyright   bit2 - data_alignment_indicator   bit3 - PES_priority   bit4-bit5 - PES_scrambling control.   bit6-bit7 - reserved   

In C I might do something like the following:

struct PESHeader  {     unsigned reserved:2;     unsigned scrambling_control:2;     unsigned priority:1;     unsigned data_alignment_indicator:1;     unsigned copyright:1;     unsigned original_or_copy:1; }; 

Is there any way to do this in C# that would enable me to access the bits using the struct dereferencing dot operator?

For a couple of structures, I can just do bit shifting wrapped in an accessor function.

I have loads of structures to handle in this way, so I'm looking for something that's easier to read and quicker to write.

回答1:

I'd probably knock together something using attributes, then a conversion class to convert suitably attributed structures to the bitfield primitives. Something like...

using System;  namespace BitfieldTest {     [global::System.AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]     sealed class BitfieldLengthAttribute : Attribute     {         uint length;          public BitfieldLengthAttribute(uint length)         {             this.length = length;         }          public uint Length { get { return length; } }     }      static class PrimitiveConversion     {         public static long ToLong(T t) where T : struct         {             long r = 0;             int offset = 0;              // For every field suitably attributed with a BitfieldLength             foreach (System.Reflection.FieldInfo f in t.GetType().GetFields())             {                 object[] attrs = f.GetCustomAttributes(typeof(BitfieldLengthAttribute), false);                 if (attrs.Length == 1)                 {                     uint fieldLength  = ((BitfieldLengthAttribute)attrs[0]).Length;                      // Calculate a bitmask of the desired length                     long mask = 0;                     for (int i = 0; i = 0; i--)             {                 Console.Write( ((l & (1l  0) ? "1" : "0");             }              Console.WriteLine();              return;         }     } } 

Which produces the expected ...000101011. Of course, it needs more error checking and a slightly saner typing, but the concept is (I think) sound, reusable, and lets you knock out easily maintained structures by the dozen.

adamw



回答2:

By using an enum you can do this, but will look awkward.

[Flags] public enum PESHeaderFlags {     IsCopy = 1, // implied that if not present, then it is an original     IsCopyrighted = 2,     IsDataAligned = 4,     Priority = 8,     ScramblingControlType1 = 0,     ScramblingControlType2 = 16,     ScramblingControlType3 = 32,     ScramblingControlType4 = 16+32,     ScramblingControlFlags = ScramblingControlType1 | ScramblingControlType2 | ... ype4     etc. } 


回答3:

You want StructLayoutAttribute

[StructLayout(LayoutKind.Explicit, Size=1, CharSet=CharSet.Ansi)] public struct Foo  { [FieldOffset(0)]public byte original_or_copy;    [FieldOffset(0)]public byte copyright;   [FieldOffset(0)]public byte data_alignment_indicator;    [FieldOffset(0)]public byte PES_priority;    [FieldOffset(0)]public byte PES_scrambling_control;    [FieldOffset(0)]public byte reserved;  } 

This is really a union but you can use it as a bitfield--you just have to be conscious of where in the byte the bits for each field are supposed to be. Utility functions and/or constants to AND against can help.

const byte _original_or_copy = 1; const byte _copyright        = 2;  //bool ooo = foo.original_or_copy(); static bool original_or_copy(this Foo foo)  { return  (foo.original_or_copy & _original_or_copy)  == original_or_copy; }     

There is also LayoutKind.Sequential which will allow you to do it the C way.



回答4:

As Christophe Lambrechts suggested BitVector32 provides a solution. Jitted performance should be adequate, but don't know for sure. Here's the code illustrating this solution:

public struct rcSpan {     //C# Spec 10.4.5.1: The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration.     internal static readonly BitVector32.Section sminSection = BitVector32.CreateSection(0x1FFF);     internal static readonly BitVector32.Section smaxSection = BitVector32.CreateSection(0x1FFF, sminSection);     internal static readonly BitVector32.Section areaSection = BitVector32.CreateSection(0x3F, smaxSection);      internal BitVector32 data;      //public uint smin : 13;      public uint smin     {         get { return (uint)data[sminSection]; }         set { data[sminSection] = (int)value; }     }      //public uint smax : 13;      public uint smax     {         get { return (uint)data[smaxSection]; }         set { data[smaxSection] = (int)value; }     }      //public uint area : 6;      public uint area     {         get { return (uint)data[areaSection]; }         set { data[areaSection] = (int)value; }     } } 

You can do a lot this way. You can do even better without using BitVector32, by providing handmade accessors for every field:

public struct rcSpan2 {     internal uint data;      //public uint smin : 13;      public uint smin     {         get { return data & 0x1FFF; }         set { data = (data & ~0x1FFFu ) | (value & 0x1FFF); }     }      //public uint smax : 13;      public uint smax     {         get { return (data >> 13) & 0x1FFF; }         set { data = (data & ~(0x1FFFu > 26) & 0x3F; }         set { data = (data & ~(0x3F 

Surprisingly this last, handmade solution seems to be the most convenient, least convoluted, and the shortest one. That's of course only my personal preference.



回答5:

You could also use the BitVector32 and especially the Section struct. The example is very good.



回答6:

One more based off of Zbyl's answer. This one is a little easier to change around for me - I just have to adjust the sz0,sz1... and also make sure mask# and loc# are correct in the Set/Get blocks.

Performance wise, it should be the same as they both resolved to 38 MSIL statements. (constants are resolved at compile time)

public struct MyStruct {     internal uint raw;      const int sz0 = 4, loc0 = 0,          mask0 = ((1 > loc0; }         set { raw = (uint)(raw & ~mask0 | (value > loc1; }         set { raw = (uint)(raw & ~mask1 | (value > loc2; }         set { raw = (uint)(raw & ~mask2 | (value > loc3); }         set { raw = (uint)(raw & ~mask3 | (value 


回答7:

While it is a class, using BitArray seems like the way to least reinvent the wheel. Unless you're really pressed for performance, this is the simplest option. (Indexes can be referenced with the [] operator.)



回答8:

A flags enum can work too, I think, if you make it a byte enum:

[Flags] enum PesHeaders : byte { /* ... */ } 


回答9:

Could an Enum with the Flags Attribute help maybe? See here:

What does the [Flags] Enum Attribute mean in C#?



回答10:

I wrote one, share it, may help someone:

[global::System.AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] public sealed class BitInfoAttribute : Attribute {     byte length;     public BitInfoAttribute(byte length) {         this.length = length;     }     public byte Length { get { return length; } } }  public abstract class BitField {      public void parse(T[] vals) {         analysis().parse(this, ArrayConverter.convert(vals));     }      public byte[] toArray() {         return ArrayConverter.convert(analysis().toArray(this));     }      public T[] toArray() {         return ArrayConverter.convert(analysis().toArray(this));     }      static Dictionary bitInfoMap = new Dictionary();     private BitTypeInfo analysis() {         Type type = this.GetType();         if (!bitInfoMap.ContainsKey(type)) {             List infos = new List();              byte dataIdx = 0, offset = 0;             foreach (System.Reflection.FieldInfo f in type.GetFields()) {                 object[] attrs = f.GetCustomAttributes(typeof(BitInfoAttribute), false);                 if (attrs.Length == 1) {                     byte bitLen = ((BitInfoAttribute)attrs[0]).Length;                     if (offset + bitLen > 32) {                         dataIdx++;                         offset = 0;                     }                     infos.Add(new BitInfo(f, bitLen, dataIdx, offset));                     offset += bitLen;                 }             }             bitInfoMap.Add(type, new BitTypeInfo(dataIdx + 1, infos.ToArray()));         }         return bitInfoMap[type];     } }  class BitTypeInfo {     public int dataLen { get; private set; }     public BitInfo[] bitInfos { get; private set; }      public BitTypeInfo(int _dataLen, BitInfo[] _bitInfos) {         dataLen = _dataLen;         bitInfos = _bitInfos;     }      public uint[] toArray(T obj) {         uint[] datas = new uint[dataLen];         foreach (BitInfo bif in bitInfos) {             bif.encode(obj, datas);         }         return datas;     }      public void parse(T obj, uint[] vals) {         foreach (BitInfo bif in bitInfos) {             bif.decode(obj, vals);         }     } }  class BitInfo {      private System.Reflection.FieldInfo field;     private uint mask;     private byte idx, offset, shiftA, shiftB;     private bool isUnsigned = false;      public BitInfo(System.Reflection.FieldInfo _field, byte _bitLen, byte _idx, byte _offset) {         field = _field;         mask = (uint)(((1 > shiftB, field.FieldType));         } else {             field.SetValue(obj, Convert.ChangeType((((int)(datas[idx] & mask)) > shiftB, field.FieldType));         }     } }  public class ArrayConverter {     public static T[] convert(uint[] val) {         return convert(val);     }      public static T1[] convert(T0[] val) {         T1[] rt = null;         // type is same or length is same         // refer to http://stackoverflow.com/questions/25759878/convert-byte-to-sbyte         if (typeof(T0) == typeof(T1)) {              rt = (T1[])(Array)val;         } else {             int len = Buffer.ByteLength(val);             int w = typeWidth();             if (w == 1) { // bool                 rt = new T1[len * 8];             } else if (w == 8) {                 rt = new T1[len];             } else { // w > 8                 int nn = w / 8;                 int len2 = (len / nn) + ((len % nn) > 0 ? 1 : 0);                 rt = new T1[len2];             }              Buffer.BlockCopy(val, 0, rt, 0, len);         }         return rt;     }      public static string toBinary(T[] vals) {         StringBuilder sb = new StringBuilder();         int width = typeWidth();         int len = Buffer.ByteLength(vals);         for (int i = len-1; i >=0; i--) {             sb.Append(Convert.ToString(Buffer.GetByte(vals, i), 2).PadLeft(8, '0')).Append(" ");         }         return sb.ToString();     }      private static int typeWidth() {         int rt = 0;         if (typeof(T) == typeof(bool)) { // x             rt = 1;         } else if (typeof(T) == typeof(byte)) { // x             rt = 8;         } else if (typeof(T) == typeof(sbyte)) {             rt = 8;         } else if (typeof(T) == typeof(ushort)) { // x             rt = 16;         } else if (typeof(T) == typeof(short)) {             rt = 16;         } else if (typeof(T) == typeof(char)) {             rt = 16;         } else if (typeof(T) == typeof(uint)) { // x             rt = 32;         } else if (typeof(T) == typeof(int)) {             rt = 32;         } else if (typeof(T) == typeof(float)) {             rt = 32;         } else if (typeof(T) == typeof(ulong)) { // x             rt = 64;         } else if (typeof(T) == typeof(long)) {             rt = 64;         } else if (typeof(T) == typeof(double)) {             rt = 64;         } else {             throw new Exception("Unsupport type : " + typeof(T).Name);         }         return rt;     } } 

and the usage:

class MyTest01 : BitField {     [BitInfo(3)]     public bool d0;     [BitInfo(3)]     public short d1;     [BitInfo(3)]     public int d2;     [BitInfo(3)]     public int d3;     [BitInfo(3)]     public int d4;     [BitInfo(3)]     public int d5;      public MyTest01(bool _d0, short _d1, int _d2, int _d3, int _d4, int _d5) {         d0 = _d0;         d1 = _d1;         d2 = _d2;         d3 = _d3;         d4 = _d4;         d5 = _d5;     }      public MyTest01(byte[] datas) {         parse(datas);     }      public new string ToString() {         return string.Format("d0: {0}, d1: {1}, d2: {2}, d3: {3}, d4: {4}, d5: {5} \r\nbinary => {6}",             d0, d1, d2, d3, d4, d5, ArrayConverter.toBinary(toArray()));     } };  class MyTest02 : BitField {     [BitInfo(5)]     public bool val0;     [BitInfo(5)]     public byte val1;     [BitInfo(15)]     public uint val2;     [BitInfo(15)]     public float val3;     [BitInfo(15)]     public int val4;     [BitInfo(15)]     public int val5;     [BitInfo(15)]     public int val6;      public MyTest02(bool v0, byte v1, uint v2, float v3, int v4, int v5, int v6) {         val0 = v0;         val1 = v1;         val2 = v2;         val3 = v3;         val4 = v4;         val5 = v5;         val6 = v6;     }      public MyTest02(byte[] datas) {         parse(datas);     }      public new string ToString() {         return string.Format("val0: {0}, val1: {1}, val2: {2}, val3: {3}, val4: {4}, val5: {5}, val6: {6}\r\nbinary => {7}",             val0, val1, val2, val3, val4, val5, val6, ArrayConverter.toBinary(toArray()));     } }  public class MainClass {      public static void Main(string[] args) {         MyTest01 p = new MyTest01(false, 1, 2, 3, -1, -2);         Debug.Log("P:: " + p.ToString());         MyTest01 p2 = new MyTest01(p.toArray());         Debug.Log("P2:: " + p2.ToString());          MyTest02 t = new MyTest02(true, 1, 12, -1.3f, 4, -5, 100);         Debug.Log("t:: " + t.ToString());         MyTest02 t2 = new MyTest02(t.toArray());         Debug.Log("t:: " + t.ToString());          Console.Read();         return;     } } 


文章来源: Bit fields in C#
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!