C# 在C语言中使用任意位位置和长度跨字节边界提取值#

C# 在C语言中使用任意位位置和长度跨字节边界提取值#,c#,networking,binary,bit-manipulation,C#,Networking,Binary,Bit Manipulation,我目前正在开发一个网络工具,它需要解码/编码一个特定的协议,该协议将字段打包到任意位置的密集位数组中。例如,协议的一部分使用3个字节来表示多个不同的字段: Bit Position(s) Length (In Bits) Type 0 1 bool 1-5 5 int 6-13 8 int 14-22

我目前正在开发一个网络工具,它需要解码/编码一个特定的协议,该协议将字段打包到任意位置的密集位数组中。例如,协议的一部分使用3个字节来表示多个不同的字段:

Bit Position(s)  Length (In Bits)    Type
0                1                   bool
1-5              5                   int
6-13             8                   int
14-22            9                   uint
23               1                   bool
如您所见,几个字段跨越多个字节。许多(大多数)也比用来表示它们的内置类型短,例如第一个int字段只有5位长。在这些情况下,目标类型(如Int32或Int16)的最高有效位应填充0以弥补差异

我的问题是,我在处理此类数据时遇到了困难。具体地说,我很难弄清楚如何有效地获取任意长度的位数组,用源缓冲区中的适当位填充它们,填充它们以匹配目标类型,并将填充的位数组转换为目标类型。在理想情况下,我可以使用上面示例中的字节[3]调用类似
GetInt32(byte[]bytes,int startBit,int length)
的方法

我发现最接近实际情况的是一个类,但它似乎希望单个值排列在字节/字边界上(类的半流式/半索引访问约定使它有点混乱)

我自己的第一次尝试是使用这个类,但结果证明有点笨拙。将缓冲区中的所有位填充到一个大的
位数组
,只将您想要的位从源
位数组
传输到一个新的临时
位数组
,然后将其转换为目标值非常简单……但这似乎是错误的,而且非常耗时

我现在考虑一个类,它引用(或创建)源/目标字节[]缓冲区以及偏移量,并为某些目标类型提供get和set方法。棘手的部分是获取/设置值可能跨越多个字节

class BitField
{
    private readonly byte[] _bytes;
    private readonly int _offset;

    public BitField(byte[] bytes)
        : this(bytes, 0)
    {
    }

    public BitField(byte[] bytes, int offset)
    {
        _bytes = bytes;
        _offset = offset;
    }

    public BitField(int size)
        : this(new byte[size], 0)
    {
    }

    public bool this[int bit]
    {
        get { return IsSet(bit); }
        set { if (value) Set(bit); else Clear(bit); }
    }

    public bool IsSet(int bit)
    {
        return (_bytes[_offset + (bit / 8)] & (1 << (bit % 8))) != 0;
    }

    public void Set(int bit)
    {
        _bytes[_offset + (bit / 8)] |= unchecked((byte)(1 << (bit % 8)));
    }

    public void Clear(int bit)
    {
        _bytes[_offset + (bit / 8)] &= unchecked((byte)~(1 << (bit % 8)));
    }

    //startIndex = the index of the bit at which to start fetching the value
    //length = the number of bits to include - may be less than 32 in which case
    //the most significant bits of the target type should be padded with 0
    public int GetInt32(int startIndex, int length)
    {
        //NEED CODE HERE
    }

    //startIndex = the index of the bit at which to start storing the value
    //length = the number of bits to use, if less than the number of bits required
    //for the source type, precision may be lost
    //value = the value to store
    public void SetValue(int startIndex, int length, int value)
    {
        //NEED CODE HERE
    }

    //Other Get.../Set... methods go here
}
类位字段
{
专用只读字节[]_字节;
专用只读整数偏移量;
公共位字段(字节[]字节)
:此(字节,0)
{
}
公共位字段(字节[]字节,整数偏移量)
{
_字节=字节;
_偏移量=偏移量;
}
公共位字段(整数大小)
:此(新字节[大小],0)
{
}
公共bool此[int位]
{
获取{返回IsSet(位);}
set{if(值)set(位);else Clear(位);}
}
公共布尔IsSet(整数位)
{

return(_bytes[_offset+(bit/8)]&(1首先,您似乎已经在类中重新发明了轮子。至于实际查找特定位字段的值,我认为这可以通过以下伪代码的一点数学魔法轻松实现:

  • 从所选内容中最远的数字开始(startIndex+长度)
  • 如果设置了,则添加2^(与数字的距离)。在这种情况下,它将是0(mostDistance-self=0)。因此添加2^(1)
  • 向左移动一位
  • 对所需长度的每个数字重复此操作
  • 在这种情况下,如果您有这样一个位数组:

    10001010

    如果你想要数字0-3的值,你会得到如下结果:

    [Index 3]   [Index 2]   [Index 1]   [Index 0]
    (3 - 3)     (3 - 2)     (3 - 1)     (3 - 0)
    =============================================
    (0 * 2^0) + (0 * 2^1) + (0 * 2^2) + (1 * 2^3) = 8
    

    因为1000(二进制)==8,所以数学是正确的。

    仅使用简单的位移位来计算值有什么问题

    int data = Convert.ToInt32( "110000000010000000100001", 2 );
    
    bool v1 = ( data & 1 ) == 1; // True
    int v2 = ( data >> 1 ) & 0x1F; // 16
    int v3 = ( data >> 6 ) & 0xFF; // 128
    uint v4 = (uint )( data >> 14 ) & 0x1FF; // 256
    bool v5 = ( data >> 23 ) == 1; // True
    

    是一篇很好的文章,涵盖了这一主题。它是用C编写的,但同样的概念仍然适用。

    如果您的数据包总是小于8或4字节,那么将每个数据包存储在
    Int32
    Int64
    中会更容易。字节数组只会使事情复杂化。您必须注意高端存储与低端存储

    然后,对于一个3字节的包:

    public static void SetValue(Int32 message, int startIndex, int length, int value)
    {
       // we want lengthx1
       int mask = (1 << length) - 1;     
       value = value & mask;  // or check and throw
    
       int offset = 24 - startIndex - length;   // 24 = 3 * 8
       message = message | (value << offset);
    }
    
    publicstaticvoidsetvalue(Int32消息,intstartindex,intlength,intvalue)
    {
    //我们想要长度1
    
    整数掩码=(1正如所承诺的,这里是我为此目的创建的类。它将在可选指定的索引处包装一个任意字节数组,并允许在位级别进行读取/写入。它提供了从其他字节数组读取/写入任意位块的方法,或使用用户定义的偏移量和长度。它非常适合我的情况,解决了我上面提出的确切问题。但是,它确实有两个缺点。第一,它显然没有大量的文档记录-我只是没有时间。第二,它没有边界或其他检查。它目前还要求库提供endian转换。综上所述,希望这可以帮助解决或作为其他具有类似用例的人的起点

    internal class BitField
    {
        private readonly byte[] _bytes;
        private readonly int _offset;
        private EndianBitConverter _bitConverter = EndianBitConverter.Big;
    
        public BitField(byte[] bytes)
            : this(bytes, 0)
        {
        }
    
        //offset = the offset (in bytes) into the wrapped byte array
        public BitField(byte[] bytes, int offset)
        {
            _bytes = bytes;
            _offset = offset;
        }
    
        public BitField(int size)
            : this(new byte[size], 0)
        {
        }
    
        //fill == true = initially set all bits to 1
        public BitField(int size, bool fill)
            : this(new byte[size], 0)
        {
            if (!fill) return;
            for(int i = 0 ; i < size ; i++)
            {
                _bytes[i] = 0xff;
            }
        }
    
        public byte[] Bytes
        {
            get { return _bytes; }
        }
    
        public int Offset
        {
            get { return _offset; }
        }
    
        public EndianBitConverter BitConverter
        {
            get { return _bitConverter; }
            set { _bitConverter = value; }
        }
    
        public bool this[int bit]
        {
            get { return IsBitSet(bit); }
            set { if (value) SetBit(bit); else ClearBit(bit); }
        }
    
        public bool IsBitSet(int bit)
        {
            return (_bytes[_offset + (bit / 8)] & (1 << (7 - (bit % 8)))) != 0;
        }
    
        public void SetBit(int bit)
        {
            _bytes[_offset + (bit / 8)] |= unchecked((byte)(1 << (7 - (bit % 8))));
        }
    
        public void ClearBit(int bit)
        {
            _bytes[_offset + (bit / 8)] &= unchecked((byte)~(1 << (7 - (bit % 8))));
        }
    
        //index = the index of the source BitField at which to start getting bits
        //length = the number of bits to get
        //size = the total number of bytes required (0 for arbitrary length return array)
        //fill == true = set all padding bits to 1
        public byte[] GetBytes(int index, int length, int size, bool fill)
        {
            if(size == 0) size = (length + 7) / 8;
            BitField bitField = new BitField(size, fill);
            for(int s = index, d = (size * 8) - length ; s < index + length && d < (size * 8) ; s++, d++)
            {
                bitField[d] = IsBitSet(s);
            }
            return bitField._bytes;
        }
    
        public byte[] GetBytes(int index, int length, int size)
        {
            return GetBytes(index, length, size, false);
        }
    
        public byte[] GetBytes(int index, int length)
        {
            return GetBytes(index, length, 0, false);
        }
    
        //bytesIndex = the index (in bits) into the bytes array at which to start copying
        //index = the index (in bits) in this BitField at which to put the value
        //length = the number of bits to copy from the bytes array
        public void SetBytes(byte[] bytes, int bytesIndex, int index, int length)
        {
            BitField bitField = new BitField(bytes);
            for (int i = 0; i < length; i++)
            {
                this[index + i] = bitField[bytesIndex + i];
            }
        }
    
        public void SetBytes(byte[] bytes, int index, int length)
        {
            SetBytes(bytes, 0, index, length);
        }
    
        public void SetBytes(byte[] bytes, int index)
        {
            SetBytes(bytes, 0, index, bytes.Length * 8);
        }
    
        //UInt16
    
        //index = the index (in bits) at which to start getting the value
        //length = the number of bits to use for the value, if less than required the value is padded with 0
        public ushort GetUInt16(int index, int length)
        {
            return _bitConverter.ToUInt16(GetBytes(index, length, 2), 0);
        }
    
        public ushort GetUInt16(int index)
        {
            return GetUInt16(index, 16);
        }
    
        //valueIndex = the index (in bits) of the value at which to start copying
        //index = the index (in bits) in this BitField at which to put the value
        //length = the number of bits to copy from the value
        public void Set(ushort value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(ushort value, int index)
        {
            Set(value, 0, index, 16);
        }
    
        //UInt32
    
        public uint GetUInt32(int index, int length)
        {
            return _bitConverter.ToUInt32(GetBytes(index, length, 4), 0);
        }
    
        public uint GetUInt32(int index)
        {
            return GetUInt32(index, 32);
        }
    
        public void Set(uint value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(uint value, int index)
        {
            Set(value, 0, index, 32);
        }
    
        //UInt64
    
        public ulong GetUInt64(int index, int length)
        {
            return _bitConverter.ToUInt64(GetBytes(index, length, 8), 0);
        }
    
        public ulong GetUInt64(int index)
        {
            return GetUInt64(index, 64);
        }
    
        public void Set(ulong value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(ulong value, int index)
        {
            Set(value, 0, index, 64);
        }
    
        //Int16
    
        public short GetInt16(int index, int length)
        {
            return _bitConverter.ToInt16(GetBytes(index, length, 2, IsBitSet(index)), 0);
        }
    
        public short GetInt16(int index)
        {
            return GetInt16(index, 16);
        }
    
        public void Set(short value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(short value, int index)
        {
            Set(value, 0, index, 16);
        }
    
        //Int32
    
        public int GetInt32(int index, int length)
        {
            return _bitConverter.ToInt32(GetBytes(index, length, 4, IsBitSet(index)), 0);
        }
    
        public int GetInt32(int index)
        {
            return GetInt32(index, 32);
        }
    
        public void Set(int value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(int value, int index)
        {
            Set(value, 0, index, 32);
        }
    
        //Int64
    
        public long GetInt64(int index, int length)
        {
            return _bitConverter.ToInt64(GetBytes(index, length, 8, IsBitSet(index)), 0);
        }
    
        public long GetInt64(int index)
        {
            return GetInt64(index, 64);
        }
    
        public void Set(long value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(long value, int index)
        {
            Set(value, 0, index, 64);
        }
    
        //Char
    
        public char GetChar(int index, int length)
        {
            return _bitConverter.ToChar(GetBytes(index, length, 2), 0);
        }
    
        public char GetChar(int index)
        {
            return GetChar(index, 16);
        }
    
        public void Set(char value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(char value, int index)
        {
            Set(value, 0, index, 16);
        }
    
        //Bool
    
        public bool GetBool(int index, int length)
        {
            return _bitConverter.ToBoolean(GetBytes(index, length, 1), 0);
        }
    
        public bool GetBool(int index)
        {
            return GetBool(index, 8);
        }
    
        public void Set(bool value, int valueIndex, int index, int length)
        {
            SetBytes(_bitConverter.GetBytes(value), valueIndex, index, length);
        }
    
        public void Set(bool value, int index)
        {
            Set(value, 0, index, 8);
        }
    
        //Single and double precision floating point values must always use the correct number of bits
        public float GetSingle(int index)
        {
            return _bitConverter.ToSingle(GetBytes(index, 32, 4), 0);
        }
    
        public void SetSingle(float value, int index)
        {
            SetBytes(_bitConverter.GetBytes(value), 0, index, 32);
        }
    
        public double GetDouble(int index)
        {
            return _bitConverter.ToDouble(GetBytes(index, 64, 8), 0);
        }
    
        public void SetDouble(double value, int index)
        {
            SetBytes(_bitConverter.GetBytes(value), 0, index, 64);
        }
    }
    
    内部类位字段
    {
    专用只读字节[]_字节;
    专用只读整数偏移量;
    私有EndianBitConverter _bitConverter=EndianBitConverter.Big;
    公共位字段(字节[]字节)
    :此(字节,0)
    {
    }
    //偏移量=包装字节数组中的偏移量(以字节为单位)
    公共位字段(字节[]字节,整数偏移量)
    {
    _字节=字节;
    _偏移量=偏移量;
    }
    公共位字段(整数大小)
    :此(新字节[大小],0)
    {
    }
    //fill==true=最初将所有位设置为1
    公共位字段(整数大小,布尔填充)
    :此(新字节[大小],0)
    {
    如果(!填写)返回;
    对于(int i=0;i