Bit fields in C#
我有一个需要填充并写入磁盘的结构(实际上是几个)。
一个例子是:
1 2 3 4 5 6 7
| byte-6
bit0 - original_or_copy
bit1 - copyright
bit2 - data_alignment_indicator
bit3 - PES_priority
bit4-bit5 - PES_scrambling control.
bit6-bit7 - reserved |
在C语言中,我可能会执行以下操作:
1 2 3 4 5 6 7 8
| 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#中是否有任何方法可以使我使用结构解引用点运算符访问这些位?
对于两个结构,我只需要package访问器函数中的位移位即可。
我有很多结构可以用这种方式处理,所以我正在寻找更易于阅读和编写的东西。
我可能会使用属性将某些东西组合在一起,然后是一个转换类,以将适当地归因于结构的结构转换为位域基元。像...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| 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;
}
}
} |
产生预期的... 000101011。当然,它需要更多的错误检查和更精巧的键入,但是(我认为)该概念是合理的,可重用的,并且可以让您轻松打出一打维护的结构。
adamw
通过使用枚举,您可以执行此操作,但是看起来很尴尬。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [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
1 2 3 4 5 6 7 8 9
| [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相对的常量可能会有所帮助。
1 2 3 4 5 6 7
| 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提供了一种解决方案。抖动性能应该足够,但是不
以下是说明此解决方案的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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的情况下做得更好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 ...并确保Set / Get块中的mask#和loc#是正确的。
在性能方面,应该与它们都解析为38个MSIL语句相同。 (常量在编译时解析)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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); }
}
} |
您也可以使用BitVector32,尤其是Section struct。这个例子很好。
虽然它是一类,但使用BitArray似乎是最少重新发明轮子的方法。除非您对性能有真正的要求,否则这是最简单的选择。 (可以使用[]运算符引用索引。)
我认为,如果将它设为字节枚举,那么标记枚举也可以工作:
1
| [Flags] enum PesHeaders : byte { /* ... */ } |
带有Flags属性的枚举可以帮助吗?参见此处:
[Flags]枚举属性在C#中是什么意思?
我发现自己对这些辅助功能很满意:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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;
} |
然后:
1 2 3 4 5 6 7
| uint the_word;
public uint Itemx
{
get { return ReadBits(the_word, 5, 2); }
set { the_word = SetBits(the_word, value, 5, 2) }
} |
我写了一个,分享了,可能对某人有帮助:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| [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;
}
} |
及其用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| 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} \
\
binary => {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}\
\
binary => {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;
}
} |
|