3

私は、バイト境界を越えて大量のデータをビットにパッキングする仕様に準拠する必要があるツールに取り組んでいます。例: 2 バイトは 2 フィールド、10 ビット値、6 ビット許容値をエンコードします。他のフィールドは 2 ~ 4 バイトにまたがり、さらに多くのフィールドに分割される場合があります。

C# と戦って (C++ のように) ビットフィールドで構造体を取得しようとするのではなく、別の方法として、データを送信する直前/受信した直後に一般的なビット パッキング/アンパッキング関数を作成し、標準型を使用して C# ですべてのデータを操作することを考えました。 : byte、short、int、long など

私は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; 
}

このような方法でメソッドを呼び出して、(LSB から) から 10 ビットを取り出しdata_indata_out次の 6 ビットを からdata_in取り出し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
    }
}

byteushortuint、のすべての可能な組み合わせに対して関数を記述する必要はありませんがulong、それは別の方法だと思います。

私はすでにBitConverterクラスを見ましたが、それはビットを操作しないバイト配列用です。また、私は:やのようなことはできないことも理解しているので、提案をお待ちしています。where T : INumericwhere T : System.ValueType

ありがとう!

4

3 に答える 3

3

ご存じのように、 を実行することはできませんwhere T : INumeric。そのため、何を記述しても、さまざまな数値型をサポートするために、おそらくいくつかのバリエーションが必要になるでしょう。

BitArray必要に応じて、他のデータ型との間で変換するために、おそらく aおよび write メソッドを使用します。その場合、型の組み合わせごとに 1 つではなく、各数値型との間で最大 1 つのメソッドが必要になります。(C# には8 に近い整数型があるため、最悪の場合は 8*8=64 ではなく、8+8=16 程度になります)

手動でコピー/貼り付けして何かが変更されたときにそれを更新するという考えが気に入らない場合は、おそらくT4 テンプレートを使用して 8 っぽい整数型のメソッドを生成できます。

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. 同様に、出力用の割り当ても多数あります。そうしないでください。宣言で出力の型を type として指定していますU

    // don't do this
    if(count <= 8)
         output = new byte();
    if (...) //etc 
    // do this
    output = new U();
    
  3. この2つを修正しても、どこまで行けるかはまだわかりません。ジェネリック型から操作を推測することはできません。また、それらに値を割り当てることもできないと思います。

    // 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 []を行い、書き込みに使用しました。(ここでの欠点は、操作ごとに構造体を 2 回完全にコピーするため、あまりパフォーマンスが高くないことです)

BitConverter または一連の関数を使用して、いくつかの既知の型 (例: Int32、Int16、Int64 など) とやり取りすると、実行速度が大幅に向上する可能性があります。

于 2013-11-05T16:03:59.477 に答える