3

我正在开发一种工具,该工具必须符合规范,该规范将大量数据打包成跨字节边界的位。示例:2 字节编码 2 个字段,10 位值,6 位容差。其他字段可能跨越 2-4 个字节并分成更多字段。

与其与 C# 斗争并尝试使用位域(如在 C++ 中)获得结构,我想另一种选择是在发送/接收数据之前创建通用位打包/解包函数,并使用标准类型在 C# 中处理所有数据:字节,短,整数,长等。

我是 C# 新手,所以我不确定解决这个问题的最佳方法。从我读过的内容来看,unsafe不鼓励与指针一起使用,但我尝试使用泛型类型的尝试失败了:

private static bool GetBitsFromByte<T,U>(T input, byte count, out U output, byte start = 0) where T:struct where U:struct
{
    if (input == default(T))
        return false;

    if( (start + count) > Marshal.SizeOf(input))
        return false;

    if(count > Marshal.SizeOf(output))
        return false;

    // I'd like to setup the correct output container based on the
    // number of bits that are needed
    if(count <= 8)
        output = new byte();
    else if (count <= 16)
        output = new UInt16();
    else if (count <= 32)
        output = new UInt32();
    else if (count <= 64)
        output = new UInt64();
    else
        return false;

    output = 0; // Init output

    // Copy bits out in order
    for (int i = start; i < count; i++)
    {
        output |= (input & (1 << i));  // This is not possible with generic types from my understanding
    }
    return true; 
}

我会用类似这样的方法调用该方法,将 10 位(来自 LSB)从data_ininto中拉出data_out,接下来的 6 位从data_ininto中拉出next_data_out

Uint32 data_in = 0xdeadbeef;
Uint16 data_out;
byte next_data_out;
if(GetBitsFromByte<Uint32,Uint16>(data_in, 10, out data_out, 0))
{
    // data_out should now = 0x2EF
    if(GetBitsFromByte<Uint32,byte>(data_in, 6, out next_data_out, data_out.Length))
    {
        // next_data_out should now = 0x2F
    }
}

我宁愿不必为byte, ushort, uint,的所有可能组合编写函数ulong,尽管我想这是另一种选择。

我已经看过BitConverter类,但那是针对不操作位的字节数组。我也明白我不能做类似的事情:where T : INumericor where T : System.ValueType,所以我愿意接受建议。

谢谢!

4

3 回答 3

3

如您所知,您不能这样做where T : INumeric,因此无论您编写什么,都可能必须有一些变化来支持不同的数字类型。

我可能会根据需要使用 aBitArray和 write 方法与您的其他数据类型进行转换。然后,您最多需要一种方法往返于每种数字类型,而不是每种类型组合都需要一种方法。(C# 中有8 种整数类型,所以最坏的情况是 8+8=16,而不是 8*8=64)

如果您不喜欢手动复制/粘贴的想法,并且在某些情况发生变化时更新它,您可能可以使用T4 模板来生成 8-ish 整数类型的方法。

uint data_in = 0xdeadbeef;
ushort data_out;
byte next_data_out;
// pay attention to BitConverter.IsLittleEndian here!
// you might need to write your own conversion methods,
// or do a Reverse() or find a better library
var bits = new BitArray(BitConverter.GetBytes(data_in));
if (bits.TryConvertToUInt16(out data_out, 10))
{
    Console.WriteLine(data_out.ToString("X")); // 2EF
    if (bits.TryConvertToByte(out next_data_out, 6, 10))
    {
        Console.WriteLine(next_data_out.ToString("X")); // 2F
    }
}


private static bool Validate(BitArray bits, int len, int start, int size)
{
    return len < size * 8 && bits.Count > start + len;
}
public static bool TryConvertToUInt16(this BitArray bits, out ushort output, int len, int start = 0)
{
    output = 0;
    if (!Validate(bits, len, start, sizeof(ushort)))
        return false;
    for (int i = start; i < len + start; i++)
    {
        output |= (ushort)(bits[i] ? 1 << (i - start) : 0);
    }
    return true;
}
public static bool TryConvertToByte(this BitArray bits, out byte output, int len, int start = 0)
{
    output = 0;
    if (!Validate(bits, len, start, sizeof(byte)))
        return false;
    for (int i = start; i < len + start; i++)
    {
        output |= (byte)(bits[i] ? 1 << (i - start) : 0);
    }
    return true;
}
于 2013-11-04T21:26:07.103 回答
1

这里有几件事:

  1. 当您有一个 out 参数时,您必须在函数中的某个位置分配给它。像这样的陈述是无效的:

    if( (start + count) > Marshal.SizeOf(input))
        return false; // no assignment to output!
    
  2. 同样,您有许多输出任务。不要那样做,您在声明中将输出的类型指定为类型U

    // don't do this
    if(count <= 8)
         output = new byte();
    if (...) //etc 
    // do this
    output = new U();
    
  3. 即使纠正了这两个,我仍然不确定你能走多远。您将无法从泛型类型中推断出任何操作,而且我认为您无法为它们分配值。

    // impossible to infer from a parameter constraint of "struct" 
    output = 0; // Init output
    

因此,您可能会摆脱具有硬编码输出的版本(使 U 成为硬编码类型),但从out我的角度来看,尝试使用泛型类型似乎几乎是不可能的。

编辑:想一想,我也不确定您是否能够对通用结构执行按位运算。

于 2013-11-04T21:34:13.300 回答
0

如果你想让它在任何随机结构上工作,它有点像序列化问题。有关这方面的一些信息,请参见此线程:

如何在 C# 中将结构转换为字节数组?

这是上面的概念,稍作修改以使其通用:

class GenericSerializer <T>
{
    public BitArray ToBitArray(T input, int start, int len)
    {
        int structSize = Marshal.SizeOf(input);
        BitArray ret = new BitArray(len);
        int byteStart = start / 8;
        int byteEnd = (start + len) / 8 + 1;
        byte[] buffer = new byte[byteEnd - byteStart];

        IntPtr ptr = Marshal.AllocHGlobal(structSize);
        Marshal.StructureToPtr(input, ptr, false);
        Marshal.Copy(ptr, buffer, byteStart, buffer.Length);
        Marshal.FreeHGlobal(ptr);

        int destBit = 0;
        int sourceBit = start % 8;
        int sourceEnd = sourceBit + len;
        while (sourceBit < sourceEnd)
        {
            ret[destBit] = 0 != (buffer[sourceBit / 8] 
                & (1 << (sourceBit % 8)));
            ++sourceBit;
            ++destBit;
        }

        return ret;
    }

    public T FromBytes(byte[] arr)
    {
        IntPtr ptr = Marshal.AllocHGlobal(arr.Length);
        Marshal.Copy(arr, 0, ptr, arr.Length);

        T output = (T)Marshal.PtrToStructure(ptr, typeof(T));
        Marshal.FreeHGlobal(ptr);

        return output;
    }
}

注意:我只BitArray为读取和byte []写入使用。(这里的一个缺点是您为每个操作完全复制了两次结构,因此它不会非常高效)

使用 BitConverter 或一组函数来/从一些已知类型(例如 Int32、Int16、Int64 等)可能会运行得更快。

于 2013-11-05T16:03:59.477 回答