Question

J'ai une structure que je dois remplir et écrire sur le disque (plusieurs en fait).

Un exemple est :

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

En C, je pourrais faire quelque chose comme ceci :

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

Existe-t-il un moyen de le faire en C# qui me permettrait d'accéder aux bits à l'aide de l'opérateur point de déréférencement de structure ?

Pour quelques structures, je peux simplement effectuer un décalage de bits enveloppé dans une fonction d'accesseur.

J'ai beaucoup de structures à gérer de cette manière, donc je recherche quelque chose de plus facile à lire et de plus rapide à écrire.

Était-ce utile?

La solution

Je créerais probablement quelque chose en utilisant des attributs, puis une classe de conversion pour convertir les structures correctement attribuées en primitives de champ de bits.Quelque chose comme...

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

Ce qui produit le ...000101011 attendu.Bien sûr, cela nécessite plus de vérification des erreurs et une frappe légèrement plus saine, mais le concept est (je pense) solide, réutilisable et vous permet d'éliminer par dizaines des structures faciles à entretenir.

adamw

Autres conseils

En utilisant une énumération, vous pouvez le faire, mais cela semblera gênant.

[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.
}

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

Il s'agit en réalité d'une union, mais vous pouvez l'utiliser comme un champ de bits : il vous suffit de savoir où dans l'octet les bits de chaque champ sont censés se trouver.Des fonctions utilitaires et/ou des constantes vers ET contre peuvent aider.

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

Il existe également LayoutKind.Sequential qui vous permettra de le faire à la manière C.

Comme Christophe Lambrechts l'a suggéré, BitVector32 fournit une solution.Les performances Jitted devraient être adéquates, mais je n'en suis pas sûr.Voici le code illustrant cette 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; }
    }
}

Vous pouvez faire beaucoup de choses de cette façon.Vous pouvez faire encore mieux sans utiliser BitVector32, en fournissant des accesseurs faits à la main pour chaque champ :

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

Étonnamment, cette dernière solution artisanale semble être la plus pratique, la moins compliquée et la plus courte.Ce n'est bien sûr que ma préférence personnelle.

Un de plus basé sur la réponse de Zbyl.Celui-ci est un peu plus facile à changer pour moi - je dois juste ajuster les sz0, sz1...et assurez-vous également que mask# et loc# sont corrects dans les blocs Set/Get.

En termes de performances, cela devrait être le même car ils ont tous deux résolu 38 instructions MSIL.(les constantes sont résolues au moment de la compilation)

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

Vous pouvez également utiliser le BitVector32 et surtout le Section struct.L'exemple est très bon.

Bien qu'il s'agisse d'une classe, en utilisant BitArray Cela semble être le moyen de réinventer au moins la roue.À moins que vous ne soyez vraiment pressé par les performances, c’est l’option la plus simple.(Les index peuvent être référencés avec le [] opérateur.)

Une énumération avec l'attribut Flags pourrait-elle peut-être aider ?Vois ici:

Que signifie l'attribut [Flags] Enum en C# ?

Une énumération de drapeaux peut aussi fonctionner, je pense, si vous en faites une énumération d'octets :

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

J'en ai écrit un, partagez-le, cela peut aider quelqu'un :

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

et l'utilisation :

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

Je me sens assez à l'aise avec ces fonctions d'assistance :

uint SetBits(uint word, uint value, int pos, int size)
{
    uint mask = ((((uint)1) << size) - 1) << pos;
    word &= ~mask; //resettiamo le posizioni
    word |= (value << pos) & mask;
    return word;
}

uint ReadBits(uint word, int pos, int size)
{
    uint mask = ((((uint)1) << size) - 1) << pos;
    return (word & mask) >> pos;
}

alors:

uint the_word;

public uint Itemx
{
    get { return ReadBits(the_word, 5, 2); }
    set { the_word = SetBits(the_word, value, 5, 2) }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top