2008-08-18 4 views
60

Ho una struttura che ho bisogno di compilare e scrivere su disco (diversi in realtà).Campi bit in C#

Un esempio è:

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

In CI potrebbe fare qualcosa di simile a quanto segue:

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; 
}; 

Esiste un modo per fare questo in C# che mi avrebbe permesso di accedere ai bit utilizzando la struct dereferencing dot operator?

Per un paio di strutture, posso semplicemente eseguire lo spostamento di bit in una funzione di accesso.

Ho un sacco di strutture da gestire in questo modo, quindi sto cercando qualcosa che sia più facile da leggere e più veloce da scrivere.

risposta

46

probabilmente sarei mettere insieme qualcosa usando gli attributi, quindi una classe di conversione per convertire adeguatamente attribuita strutture ai primitivi bitfield. Qualcosa come ...

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 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 < fieldLength; i++) 
         mask |= 1 << i; 

        r |= ((UInt32)f.GetValue(t) & mask) << offset; 

        offset += (int)fieldLength; 
       } 
      } 

      return r; 
     } 
    } 

    struct PESHeader 
    { 
     [BitfieldLength(2)] 
     public uint reserved; 
     [BitfieldLength(2)] 
     public uint scrambling_control; 
     [BitfieldLength(1)] 
     public uint priority; 
     [BitfieldLength(1)] 
     public uint data_alignment_indicator; 
     [BitfieldLength(1)] 
     public uint copyright; 
     [BitfieldLength(1)] 
     public uint original_or_copy; 
    }; 

    public class MainClass 
    { 
     public static void Main(string[] args) 
     { 
      PESHeader p = new PESHeader(); 

      p.reserved = 3; 
      p.scrambling_control = 2; 
      p.data_alignment_indicator = 1; 

      long l = PrimitiveConversion.ToLong(p); 


      for (int i = 63; i >= 0; i--) 
      { 
       Console.Write(((l & (1l << i)) > 0) ? "1" : "0"); 
      } 

      Console.WriteLine(); 

      return; 
     } 
    } 
} 

Che produce l'attesa ... 000101011. Ovviamente, è necessario più controllo degli errori e una digitazione leggermente più accurata, ma il concetto è (credo) sonoro, riutilizzabile e consente di escludere le strutture mantenute con facilità a dozzine.

adamw

+0

Questo è un impressionante, davvero creativo soluzione. Molto bene! – dviljoen

+7

NOTA: Per MSDN, "Il metodo' GetFields' non restituisce campi in un ordine particolare, ad esempio in ordine alfabetico o di dichiarazione.Il tuo codice non deve dipendere dall'ordine in cui vengono restituiti i campi, poiché tale ordine varia. " Questo non causa problemi qui? –

+1

Se si crea un'interfaccia 'IBitfield'' marker (senza membri), è possibile convertire la classe 'PrimitiveConversion' in metodi di estensione per qualsiasi struttura che implementa' IBitfield'. Ad esempio: 'public static long ToLong (this IBitfield obj) {}'. Quindi, il metodo 'ToLong()' apparirà in Intellisense per gli oggetti 'IBitfield'. –

15

Volete 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; 
} 

Questo è veramente un sindacato, ma è possibile utilizzarlo come un campo di bit - devi solo essere consapevoli di dove nel byte i bit per ogni campo dovrebbero essere. Le funzioni di utilità e/o le costanti a AND contro possono aiutare.

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; 
}  

C'è anche LayoutKind.Sequential che ti permetterà di farlo in modo C.

16

Utilizzando un enum è possibile farlo, ma sembrerà imbarazzante.

[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

A bandiere enum può lavorare troppo, penso che, se si fanno un enum byte:

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

Anche se è una classe, utilizzando BitArray sembra il modo di reinventare la ruota almeno. A meno che non siate davvero sollecitati per le prestazioni, questa è l'opzione più semplice. (Gli indici possono essere consultati con l'operatore [].)

5

È anche possibile utilizzare BitVector32 e in particolare lo Section struct. L'esempio è molto buono.

13

Come suggerito da Christophe Lambrechts, BitVector32 offre una soluzione. Le prestazioni Jitted dovrebbero essere adeguate, ma non lo so per certo. Ecco il codice che illustra questa soluzione:

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; } 
    } 
} 

Si può fare molto in questo modo. Si può fare ancora meglio senza l'utilizzo di BitVector32, fornendo funzioni di accesso a mano per ogni campo:

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 << 13)) | (value & 0x1FFF) << 13; } 
    } 

    //public uint area : 6; 
    public uint area 
    { 
     get { return (data >> 26) & 0x3F; } 
     set { data = (data & ~(0x3F << 26)) | (value & 0x3F) << 26; } 
    } 
} 

Sorprendentemente quest'ultima soluzione, fatto a mano sembra essere la più conveniente, almeno contorto, e quello più breve. Questa è ovviamente solo la mia preferenza personale.

4

Un altro basato sulla risposta di Zbyl. Questo è un po 'più facile da cambiare in giro per me - Devo solo regolare sz0, sz1 ... e anche assicurarmi che mask # e loC# siano corretti nei blocchi Set/Get.

Per quanto riguarda le prestazioni, è necessario che siano entrambi risolti in 38 istruzioni MSIL. (Costanti vengono risolti al momento della compilazione)

public struct MyStruct 
{ 
    internal uint raw; 

    const int sz0 = 4, loc0 = 0,   mask0 = ((1 << sz0) - 1) << loc0; 
    const int sz1 = 4, loc1 = loc0 + sz0, mask1 = ((1 << sz1) - 1) << loc1; 
    const int sz2 = 4, loc2 = loc1 + sz1, mask2 = ((1 << sz2) - 1) << loc2; 
    const int sz3 = 4, loc3 = loc2 + sz2, mask3 = ((1 << sz3) - 1) << loc3; 

    public uint Item0 
    { 
     get { return (uint)(raw & mask0) >> loc0; } 
     set { raw = (uint)(raw & ~mask0 | (value << loc0) & mask0); } 
    } 

    public uint Item1 
    { 
     get { return (uint)(raw & mask1) >> loc1; } 
     set { raw = (uint)(raw & ~mask1 | (value << loc1) & mask1); } 
    } 

    public uint Item2 
    { 
     get { return (uint)(raw & mask2) >> loc2; } 
     set { raw = (uint)(raw & ~mask2 | (value << loc2) & mask2); } 
    } 

    public uint Item3 
    { 
     get { return (uint)((raw & mask3) >> loc3); } 
     set { raw = (uint)(raw & ~mask3 | (value << loc3) & mask3); } 
    } 
} 
+1

Ottima configurazione. Riusato con gioia;). Ho scoperto che quando il bitfield è "pieno" (ad es. Quando si imposta 'raw = uint.MaxValue') ho dovuto modificare leggermente l'ultimo elemento. O forse riguarda solo l'ultima proprietà. Non sono sicuro. Quindi, per il tuo esempio sopra, i getter di proprietà 'ItemX' assomigliano a questo:' get {return (uint) ((Raw & Mask3) >> Loc3); }. Il setter assomiglia a questo: 'set {Raw = (uint) (Raw e ~ Mask3 | (valore << Loc3) & Mask3); } 'Senza questa modifica, la trasmissione fallisce per l'ultima proprietà. – Spiralis

+0

@Spiralis: Grazie per averlo notato. L'ho aggiornato come hai detto tu e ora funziona meglio. – Sunsetquest

+0

@kvc: Grazie per la pulizia del codice. Sembra molto meglio – Sunsetquest

1

ho scritto uno, condividerle, può aiutare qualcuno:

[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>(T[] vals) { 
     analysis().parse(this, ArrayConverter.convert<T, uint>(vals)); 
    } 

    public byte[] toArray() { 
     return ArrayConverter.convert<uint, byte>(analysis().toArray(this)); 
    } 

    public T[] toArray<T>() { 
     return ArrayConverter.convert<uint, T>(analysis().toArray(this)); 
    } 

    static Dictionary<Type, BitTypeInfo> bitInfoMap = new Dictionary<Type, BitTypeInfo>(); 
    private BitTypeInfo analysis() { 
     Type type = this.GetType(); 
     if (!bitInfoMap.ContainsKey(type)) { 
      List<BitInfo> infos = new List<BitInfo>(); 

      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>(T obj) { 
     uint[] datas = new uint[dataLen]; 
     foreach (BitInfo bif in bitInfos) { 
      bif.encode(obj, datas); 
     } 
     return datas; 
    } 

    public void parse<T>(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 << _bitLen) - 1) << _offset); 
     idx = _idx; 
     offset = _offset; 
     shiftA = (byte)(32 - _offset - _bitLen); 
     shiftB = (byte)(32 - _bitLen); 

     if (_field.FieldType == typeof(bool) 
      || _field.FieldType == typeof(byte) 
      || _field.FieldType == typeof(char) 
      || _field.FieldType == typeof(uint) 
      || _field.FieldType == typeof(ulong) 
      || _field.FieldType == typeof(ushort)) { 
      isUnsigned = true; 
     } 
    } 

    public void encode(Object obj, uint[] datas) { 
     if (isUnsigned) { 
      uint val = (uint)Convert.ChangeType(field.GetValue(obj), typeof(uint)); 
      datas[idx] |= ((uint)(val << offset) & mask); 
     } else { 
      int val = (int)Convert.ChangeType(field.GetValue(obj), typeof(int)); 
      datas[idx] |= ((uint)(val << offset) & mask); 
     } 
    } 

    public void decode(Object obj, uint[] datas) { 
     if (isUnsigned) { 
      field.SetValue(obj, Convert.ChangeType((((uint)(datas[idx] & mask)) << shiftA) >> shiftB, field.FieldType)); 
     } else { 
      field.SetValue(obj, Convert.ChangeType((((int)(datas[idx] & mask)) << shiftA) >> shiftB, field.FieldType)); 
     } 
    } 
} 

public class ArrayConverter { 
    public static T[] convert<T>(uint[] val) { 
     return convert<uint, T>(val); 
    } 

    public static T1[] convert<T0, T1>(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<T1>(); 
      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>(T[] vals) { 
     StringBuilder sb = new StringBuilder(); 
     int width = typeWidth<T>(); 
     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<T>() { 
     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; 
    } 
} 

e l'utilizzo:

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; 
    } 
}