2013-11-04 25 views
3

我正在研究一個工具,該工具必須符合跨越字節邊界將大量數據打包到數據位的規範。例如:2個字節編碼2個字段,10位值,6位容差。其他字段可能會跨越2-4個字節並分解成更多字段。C# - 編寫處理位操作的通用方法

而不是與C#爭鬥並嘗試獲取位域的結構(比如在C++中),我想另一種方法是在接收數據之前/之後創建泛型位打包/解包功能,並使用C#使用標準類型:字節,短,int,長等。

我是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_in變爲data_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 
    } 
} 

我寧願沒有寫功能的byteushortuintulong所有可能的組合,但我想這是一個另類。

我已經看過BitConverter類,但這是字節數組不操縱比特。我也明白,我不能做類似:where T : INumericwhere T : System.ValueType,所以我願意提供建議。

謝謝!

+0

您是否考慮調用非託管c/C++代碼?它可能更容易。 – Jay

+0

@Jay我希望能夠在C#中完成所有工作,但如果所有其他工作都失敗了,那就是回退計劃。 – sbtkd85

+0

@neoistheone謝謝,更新 – sbtkd85

回答

3

正如你所知,你不能做where T : INumeric,所以無論你寫什麼,可能都會有一些變化來支持不同的數字類型。

我可能會使用BitArray並根據需要編寫方法來轉換/從其他數據類型轉換。那麼您最多隻需要一種來自每種數字類型的方法,而不是每種類型的組合。 (有8-ish integer types在C#中,所以最壞的情況是大約8 + 8 = 16,而不是8×8 = 64)

你很可能T4 Templates產生的8十歲上下的整數類型的方法,如果你不」 t喜歡手動複製/粘貼的想法,並在有些事情發生變化時更新。

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; 
} 
+0

我很困惑不''related_bits'拿着'bool'的IEnumerable嗎?你如何有效地將其轉換爲'ushort'? – 2013-11-04 21:38:03

+0

你可能會像使用問題的代碼一樣使用'| ='循環。 –

+0

我寧願用'byte []'和'BitConverter'來做這件事。只是覺得它會更快...... – 2013-11-04 21:45:09

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泛型類型看來幾乎是不可能的,從我的角度來看。

編輯:過來想一想,我不確定你能否在泛型結構上執行按位操作。

0

如果你想讓這個工作在任何隨機結構上,它會帶來序列化問題。看到這個線程上的一些信息:

How to convert a structure to a byte array in 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等)可能會運行得更快。