86 votes

Champs de bits en C#

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

Un exemple :

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 ce qui suit :

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 faire cela en C# qui me permettrait d'accéder aux bits en utilisant l'opérateur dot de déréférencement de structure ?

Pour quelques structures, je peux simplement faire un décalage de bits enveloppé dans une fonction d'accès.

J'ai beaucoup de structures à traiter de cette manière, et je cherche donc quelque chose qui soit plus facile à lire et plus rapide à écrire.

59voto

Adam Wright Points 31715

Je mettrais probablement au point quelque chose en utilisant des attributs, puis une classe de conversion pour convertir les structures attribuées en primitives bitfield. 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 résultat attendu ...000101011. Bien sûr, il faut plus de contrôle d'erreur et un typage un peu plus sain, mais le concept est (je pense) solide, réutilisable, et vous permet de produire des structures faciles à maintenir par douzaine.

adamw

10 votes

REMARQUE : Selon MSDN, "Le GetFields ne renvoie pas les champs dans un ordre particulier, tel que l'ordre alphabétique ou l'ordre de déclaration. Votre code ne doit pas dépendre de l'ordre dans lequel les champs sont retournés, car cet ordre varie." Cela ne pose-t-il pas un problème ici ?

1 votes

Si vous créez un IBitfield (qui n'a pas de membres), vous pouvez convertir l'interface PrimitiveConversion à des méthodes d'extension pour toute structure qui implémente la classe IBitfield . Par exemple : public static long ToLong(this IBitfield obj) {} . Ensuite, le ToLong() apparaîtra dans Intellisense pour IBitfield objets.

0 votes

Pouvez-vous inverser le processus en utilisant 'f.SetValue(t,someValue)' ? Je l'utilise pour convertir les classes de paquets en tampons de messages pour les transferts de sockets. Cela fonctionne très bien mais je ne peux pas lire les données du flux vers la structure en utilisant f.SetValue() pour une raison quelconque. Aucune erreur, mais cela ne fonctionne pas.

26voto

Lasse V. Karlsen Points 148037

Vous pouvez le faire en utilisant un enum, mais cela aura l'air bizarre.

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

24voto

Mark Cidade Points 53945

Vous voulez 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 fait d'une union, mais vous pouvez l'utiliser comme un champ de bits - vous devez juste être conscient de l'endroit où les bits de chaque champ sont censés se trouver dans l'octet. Des fonctions utilitaires et/ou des constantes pour effectuer un ET peuvent vous 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.

21voto

Zbyl Points 718

Comme l'a suggéré Christophe Lambrechts, BitVector32 offre une solution. Les performances devraient être suffisantes, 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 même faire mieux sans utiliser BitVector32, en fournissant des accesseurs artisanaux 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. Il ne s'agit bien sûr que de ma préférence personnelle.

7voto

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X