我有一个结构,我需要填充和写入磁盘(实际上几个).
一个例子是:
byte-6 bit0 - original_or_copy bit1 - copyright bit2 - data_alignment_indicator bit3 - PES_priority bit4-bit5 - PES_scrambling control. bit6-bit7 - reserved
在CI中可能会执行以下操作:
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; };
有没有办法在C#中做到这一点,使我能够使用struct dereferencing点运算符访问位?
对于几个结构,我可以在访问器函数中进行包裹移位.
我有很多结构要以这种方式处理,所以我正在寻找一些更容易阅读和更快写的东西.
我可能会使用属性拼凑一些东西,然后转换类将适当的属性结构转换为位域基元.就像是...
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 < 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; } } }
哪个产生了预期的... 000101011.当然,它需要更多的错误检查和稍微更健全的打字,但这个概念(我认为)是合理的,可重复使用的,并且让你可以轻松淘汰那些容易维护的结构.
adamw
通过使用枚举,你可以做到这一点,但看起来很尴尬.
[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. }
你想要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; }
这实际上是一个联合,但您可以将它用作位域 - 您只需要知道每个字段的位应该在字节中的哪个位置.效用函数和/或AND的常量可以提供帮助.
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; }
还有LayoutKind.Sequential,它允许你以C方式进行.
正如Christophe Lambrechts建议BitVector32提供解决方案.Jitted表现应该足够,但不确定.以下是说明此解决方案的代码:
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; } } }
你可以用这种方式做很多事情.通过为每个字段提供手工访问器,您可以在不使用BitVector32的情况下做得更好:
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; } } }
令人惊讶的是,最后一个手工制作的解决方案似乎是最方便,最简单,最短的解决方案.那当然只是我个人的偏好.
还有一个基于Zbyl的答案.这个对我来说更容易改变 - 我只需要调整sz0,sz1 ......并确保掩码#和loc#在Set/Get块中是正确的.
性能方面,它应该是相同的,因为它们都解决了38个MSIL语句.(常量在编译时解析)
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); } } }
虽然这是一门课,但使用BitArray
似乎是重新发明轮子的方法。除非您对性能有真正的要求,否则这是最简单的选择。(索引可以由[]
运算符引用。)
你也可以使用BitVector32
,特别是Section struct
.这个例子非常好.