2011-08-11 38 views
5

Trong ví dụ mã C# dưới đây, tôi có một mảng byte đã được đọc từ một socket. Tôi muốn phân tích các dữ liệu vào các lĩnh vực khác nhau của 'exampleClass' (8 byte đầu tiên vào 64-bit biến 'field1', bên cạnh 4 byte thành 32-bit biến 'field2', vv)Sao chép mảng byte vào các trường khác nhau trong lớp/struct trong C#

using System; 
namespace CsByteCopy 
{ 
    class Program 
    { 
    class ExampleClass 
    { 
     public UInt64 field1; 
     public UInt32 field2; 
     public UInt16 field3; 
     public byte[] field4 = new byte[18]; 
    } 

    static void Main(string[] args) 
    { 
     byte[] exampleData = 
     { 
     // These 8 bytes should go in 'field1' 
     0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 
     // These 4 bytes should go in 'field2' 
     0x08,0x09,0x0A,0x0B, 
     // These 2 bytes should go in 'field3' 
     0x0C,0x0D, 
     // These 18 * 1 bytes should go in 'field4' 
     0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, 
     }; 

     ExampleClass exampleClass = new ExampleClass(); 
     // Perform copy 
    } 
    } 
} 

Đã lâu rồi kể từ lần cuối tôi sử dụng C, nhưng nếu tôi nhớ lại chính xác, tôi có thể đã có được một cuộc gọi memcpy() duy nhất để điền tất cả các trường trong lớp. Cách hiệu quả nhất để điền vào các trường 'exampleClass' trong C# là gì?

Trả lời

2

Tôi nghĩ rằng bạn muốn điều này:

đây bằng cách thay đổi các bộ đệm trong Field4 bạn cũng thay đổi 3 khác mà nhiều loại cụ thể.

[StructLayout(LayoutKind.Explicit, Size=8)] 
struct UValue 
{ 
    [FieldOffset(0)] 
    public fixed byte field4[18]; // 18 bytes long 

    [FieldOffset(0)] 
    public uint64 field1; 

    [FieldOffset(8)] 
    public Uint32 field2 

    [FieldOffset(12)] 
    public Uint16 field3 

} 

Bạn có thể muốn điều này:

đây bằng cách thay đổi các bộ đệm trong fieldload bạn thay đổi những người khác như trên nhưng đó cũng là một bộ đệm byte ở cuối.

[StructLayout(LayoutKind.Explicit, Size=8)] 
struct UValue 
{ 
    [FieldOffset(0)] 
    public fixed byte fieldload[38]; // modify this to modify others 

    [FieldOffset(0)] 
    public uint64 field1; 

    [FieldOffset(8)] 
    public Uint32 field2 

    [FieldOffset(12)] 
    public Uint16 field3 

    [FieldOffset(14)] 
    public fixed byte field4[18]; // 18 bytes long 
} 
+0

Điều quan trọng cần biết là từ khóa cố định chỉ có thể được sử dụng trong ngữ cảnh không an toàn. Cố định theo nghĩa đen có nghĩa là trường này thực sự thuộc kiểu 'byte *' thay vì một byte []. –

+0

@Christopher - điều này là chính xác, thậm chí nó có thể cần một công cụ sửa đổi không an toàn cho phần khai báo stuct. Tôi đã không thử và biên dịch mã này. – Hogan

1

Một tùy chọn khác, nếu bạn có thể sử dụng cấu trúc, là So sánh mảng byte trực tiếp vào cấu trúc.

struct ExampleStruct 
{ 
    public UInt64 field1; 
    public UInt32 field2; 
    public UInt16 field3; 
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 18)] 
    public byte[] field4; 
} 

Và để có được một cấu trúc cho rằng:

var handle = GCHandle.Alloc(exampleData, GCHandleType.Pinned); 
var structure = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof (ExampleClass)); 
handle.Free(); 

structure sẽ chứa dữ liệu của bạn.

+0

Gọn gàng --- vì vậy để sao chép dữ liệu trong bạn sao chép để xử lý trước khi bạn giải phóng nó? (bạn có thể đưa ra ví dụ về điều đó không?) – Hogan

+0

@Hogan: Mục đích của GCHandle là ghim byte [] sao cho CLR/GC không di chuyển nó trong khi 'Marshal.PtrToStructure' đang hoạt động, và vì vậy chúng tôi có thể lấy địa chỉ nơi byte [] tồn tại. 'Free' là cho chính GCHandle. 'Alloc' không sao chép nội dung byte []. – vcsjones

+0

Tôi nghĩ bạn có nghĩa là "typeof (ExampleStruct)". – RenniePet

12

Bạn có nhiều tùy chọn, một trong những lựa chọn tốt nhất thường phụ thuộc vào những gì chương trình của bạn cần/có thể xử lý và chính xác tốc độ bạn muốn. Có rất nhiều bài viết giải thích các cách khác nhau mà bạn có thể điền vào một lớp hoặc một cấu trúc với dữ liệu.

Nối tiếp nhị phân là một cách. Điều đó đòi hỏi bạn viết một serializer tùy chỉnh, đó là khá dễ dàng. An MSDN article regarding that cho thấy nó không quá khó.

Bạn có thể viết một constructor tin mà mất trong mảng byte và sử dụng một BinaryReader hoặc lớp BitConverter đọc từ mảng byte (bạn phải bọc nó trong một MemoryStream cho BinaryReader) hoặc chỉ đơn giản là chuyển đổi các phần của mảng byte cho các giá trị bạn cần (BitConverter). Trong trường hợp của BitConverter, bạn cũng sẽ cần sử dụng Buffer.BlockCopy để sao chép dữ liệu còn lại của mảng byte vào trường mảng byte trong lớp của bạn

Cách thứ ba, cũng là cách nhanh nhất, là chuyển đổi lớp của bạn thành struct và sử dụng mã không an toàn để đúc mảng byte như cấu trúc đó. Một cái gì đó như thế này:

unsafe struct ExampleClass 
{ 
    public ulong field1; 
    public uint field2 
    public ushort field3 
    public fixed byte field4[18]; 

    public static ExampleClass ReadStruct(byte[] data) 
    { 
     fixed (byte* pb = &data[0]) 
     { 
      return *(ExampleClass*)pb; 
     } 
    } 
} 

Tất nhiên, mã trên chỉ hợp lệ nếu bạn có thể sử dụng mã không an toàn. Hơn nữa, việc chuyển đổi lớp thành cấu trúc có thể cũng không phải là thứ bạn đang tìm kiếm. Trong hầu hết các trường hợp, các cấu trúc được truyền theo giá trị đến hàm, để các phương thức gọi sao chép toàn bộ cấu trúc (trong trường hợp này là 32 byte) thay vì truyền tham chiếu (4 hoặc 8 byte, tùy thuộc vào kiến ​​trúc CPU) và có thể giảm hiệu quả của chương trình của bạn. Có những ngoại lệ đối với cấu trúc được truyền theo giá trị và bạn có thể sử dụng công cụ tìm kiếm yêu thích của mình cho điều đó.

Nếu bạn không thể sử dụng mã không an toàn, cũng có Marshal.PtrToStructure sẽ thực hiện giống như mã trên, nhưng chậm hơn khoảng 10 lần. Bạn cũng cần phải sử dụng thuộc tính MarshalAs để chỉ định kích thước của mảng, thay vì sử dụng từ khóa cố định (không an toàn). Tại thời điểm đó, bạn cũng có thể sử dụng BinaryReader/BitConverter, vì nó sẽ nhanh hơn so với lớp nguyên soái.

+0

+1, Câu trả lời hay, làm cách nào để so sánh với cách sử dụng LayoutKind.Explicit như trong ví dụ của tôi? (hoặc là có một lý do theo cách của tôi sẽ không làm việc?) – Hogan

+1

Yours sẽ không biên dịch, bởi vì 'byte [số bất kỳ] varName' không hợp lệ C# mã. Ngoài ra, StructLayout có nghĩa là để cho các marshaller biết thứ tự và/hoặc offsets của các lĩnh vực trong cấu trúc. Tôi nhận thấy bạn đã chỉ định cùng một giá trị bù cho hai giá trị và tôi nghĩ rằng trình khắc phục sự cố sẽ ném một lỗi cho điều đó. Nếu bạn muốn sử dụng các thuộc tính để xác định kích thước mảng, bạn có thể sử dụng thuộc tính 'MarshalAs' và có nó là nguyên soái' UnmangedType.ByValArray' với 'SizeConst' làm kích thước mảng. –

+0

Cùng một offset là hợp lệ, xem tài liệu microsoft tại đây: http://msdn.microsoft.com/en-us/library/aa288471(v=vs.71).aspx – Hogan

1

Hãy quên điều hiệu quả và làm cho mã của bạn có thể duy trì và dễ đọc trước tiên. Nếu cần thiết, hồ sơ và cải thiện.

Có gì sai khi sử dụng lớp BitConverter.

field1 = BitConverter.ToInt64(exampleData, 0) 
field2 = BitConverter.ToInt32(exampleData, 8) 
field3 = BitConverter.ToInt16(exampleData, 12) 
Array.Copy(exampleData, 14, field4, 0, 18) 
+3

"làm cho mã của bạn có thể duy trì và dễ đọc trước" - tốt, bạn đã chứng minh chính xác lý do tại sao giải pháp được đề xuất của bạn không phải là rất tốt. Nó không thể đọc được (nó chứa rất nhiều "số ma thuật"), và nó chắc chắn không thể duy trì được. Nếu tôi muốn thêm một lĩnh vực mới giữa field1 và field2 có một cơ hội 90% mà tôi muốn làm điều đó sai. – RenniePet

1

Và bây giờ cho một cái gì đó hoàn toàn khác nhau ...

này không thực sự trả lời câu hỏi của OP; thay vào đó nó là thứ mà tôi đã nấu chín để cung cấp một cách ánh xạ một cấu trúc C# trên một mảng byte và cho phép các trường riêng lẻ trong mảng byte cơ bản được đọc và ghi. Nó sử dụng mã không an toàn, và nhận được một con trỏ đến mảng byte và sau đó phôi nó vào một con trỏ đến cấu trúc ánh xạ các trường.

Điều này có thể không hiệu quả lắm, nhưng có lợi thế là cung cấp ánh xạ biểu tượng của các trường mà không sử dụng "số ma thuật" cho độ dài và độ lệch của trường. Nhưng lưu ý rằng điều này sẽ không làm việc cho trao đổi dữ liệu với một hệ thống sử dụng lớn endian thay vì ít đại diện dữ liệu endian.

Các phương thức truy cập trường được triển khai dưới dạng phương thức mở rộng. Xem TestMethod() để biết ví dụ về cách sử dụng chúng.

Điều này được lấy cảm hứng từ câu trả lời của Christopher Currens - nếu bạn thấy điều này hữu ích, hãy cho anh ấy một bài viết.

using System; 
using System.Runtime.InteropServices; 

namespace ConsoleApplication1 
{ 
    [StructLayout(LayoutKind.Sequential, Pack = 1)] 
    unsafe struct ExampleStruct 
    { 
     internal const int CField4Length = 18; 

     public UInt64 Field1; 
     public UInt32 Field2; 
     public UInt16 Field3; 
     public fixed byte Field4[CField4Length]; 
    } 

    static unsafe class ExampleStructExtensionMethods 
    { 
     public static UInt64 GetField1(this byte[] byteArray) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      return (*(ExampleStruct*)byteArrayPointer).Field1; 
     } 
     } 

     public static UInt32 GetField2(this byte[] byteArray) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      return (*(ExampleStruct*)byteArrayPointer).Field2; 
     } 
     } 

     public static UInt16 GetField3(this byte[] byteArray) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      return (*(ExampleStruct*)byteArrayPointer).Field3; 
     } 
     } 

     public static byte[] GetField4(this byte[] byteArray) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      byte[] field4 = new byte[ExampleStruct.CField4Length]; 
      for (int i = 0; i < ExampleStruct.CField4Length; i++) 
       field4[i] = (*(ExampleStruct*)byteArrayPointer).Field4[i]; 

      return field4; 
     } 
     } 

     public static void SetField1(this byte[] byteArray, UInt64 field1) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      (*(ExampleStruct*)byteArrayPointer).Field1 = field1; 
     } 
     } 

     public static void SetField2(this byte[] byteArray, UInt32 field2) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      (*(ExampleStruct*)byteArrayPointer).Field2 = field2; 
     } 
     } 

     public static void SetField3(this byte[] byteArray, UInt16 field3) 
     { 
     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      (*(ExampleStruct*)byteArrayPointer).Field3 = field3; 
     } 
     } 

     public static void SetField4(this byte[] byteArray, byte[] field4) 
     { 
     if (field4.Length != ExampleStruct.CField4Length) 
      throw new ArgumentException("Byte array must have length 18", "field4"); 

     fixed (byte* byteArrayPointer = &byteArray[0]) 
     { 
      for (int i = 0; i < ExampleStruct.CField4Length; i++) 
       (*(ExampleStruct*)byteArrayPointer).Field4[i] = field4[i]; 
     } 
     } 
    } 

    class TestProgram 
    { 
     byte[] exampleData = 
     { 
     // These 8 bytes should go in 'field1' 
     0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 
     // These 4 bytes should go in 'field2' 
     0x08,0x09,0x0A,0x0B, 
     // These 2 bytes should go in 'field3' 
     0x0C,0x0D, 
     // These 18 * 1 bytes should go in 'field4' 
     0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, 
     }; 

     public void TestMethod() 
     { 
     UInt64 field1 = exampleData.GetField1(); 
     UInt32 field2 = exampleData.GetField2(); 
     UInt16 field3 = exampleData.GetField3(); 
     byte[] field4 = exampleData.GetField4(); 

     exampleData.SetField1(++field1); 
     exampleData.SetField2(++field2); 
     exampleData.SetField3(++field3); 
     exampleData.SetField4(new byte[ExampleStruct.CField4Length] 
      { 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42 }); 
     } 
    } 
} 
0

Để sử dụng lớp thay vì cấu trúc, bạn có thể sử dụng như sau. Tôi sử dụng các yếu tố khác nhau của các câu trả lời ở trên và đặt nó lại với nhau cho một lớp học hiệu suất cao. Nếu bạn không thích tất cả các mã hóa thủ công của các phương thức Serialize/Deserialize, bạn có thể dễ dàng viết một trình tạo mã sẽ lặp qua tất cả các trường/thuộc tính và phát ra các phương thức thích hợp. Đây là mã:

public interface ISerializableClass 
{ 
    int SerializableClassSize { get; } 
    StreamInflateTest Deserialize(byte[] buffer, int startOffset); 
    byte[] Serialize(byte[] buffer, int startOffset); 
} 


public class StreamInflateTest : ISerializableClass 
{ 
    private const int _classSize = 10; 
    public float Float32Value { get; set; } 
    public Int32 Int32Value { get; set; } 
    public byte Byte8Value { get; set; } 
    public bool IsOk0 { get; set; } 
    public bool IsOk1 { get; set; } 
    public bool IsOk2 { get; set; } 
    public bool IsOk3 { get; set; } 
    public bool IsOk4 { get; set; } 

    public StreamInflateTest() 
    { 
    } 

    public int SerializableClassSize { get { return _classSize; } } 
    public StreamInflateTest(byte[] buffer, int startOffset) 
    { 
     Deserialize(buffer, startOffset); 
    } 

    public unsafe StreamInflateTest Deserialize(byte[] buffer, int startOffset) 
    { 
     fixed (byte* pb = &buffer[startOffset]) 
     { 
      Float32Value = *(float*)pb; 
      Int32Value = *(int*)(pb + 4); 
      Byte8Value = pb[8]; 
      BitField8 bitfld = new BitField8(pb[9]); 
      IsOk0 = bitfld.Bit0; 
      IsOk1 = bitfld.Bit1; 
      IsOk2 = bitfld.Bit2; 
      IsOk3 = bitfld.Bit3; 
      IsOk4 = bitfld.Bit4; 
     } 

     return this; 
    } 
    public unsafe byte[] Serialize(byte[] buffer, int startOffset) 
    { 
     fixed (byte* pb = &buffer[startOffset]) 
     { 
      *(float*)pb = Float32Value; 
      *(int*)(pb + 4) = Int32Value; 
      pb[8] = Byte8Value; 
      BitField8 bitfld = new BitField8(0) 
      { 
       Bit0 = IsOk0, 
       Bit1 = IsOk1, 
       Bit2 = IsOk2, 
       Bit3 = IsOk3, 
       Bit4 = IsOk4 
      }; 
      pb[9] = bitfld.Value; 
     } 

     return buffer; 
    } 
} 

public struct BitField8 
{ 
    public byte Value; 

    public BitField8(byte value) 
    { 
     Value = value; 
    } 

    public bool Bit0 
    { 
     get { return (Value & 0x01) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x01; 
      else 
       Value = (byte)(Value & 0xFE); // clear the bit 
     } 
    } 
    public bool Bit1 
    { 
     get { return (Value & 0x02) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x02; 
      else 
       Value = (byte)(Value & 0xFD); // clear the bit 
     } 
    } 
    public bool Bit2 
    { 
     get { return (Value & 0x04) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x04; 
      else 
       Value = (byte)(Value & 0xFB); // clear the bit 
     } 
    } 
    public bool Bit3 
    { 
     get { return (Value & 0x08) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x08; 
      else 
       Value = (byte)(Value & 0xF7); // clear the bit 
     } 
    } 
    public bool Bit4 
    { 
     get { return (Value & 0x10) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x10; 
      else 
       Value = (byte)(Value & 0xEF); // clear the bit 
     } 
    } 
    public bool Bit5 
    { 
     get { return (Value & 0x20) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x20; 
      else 
       Value = (byte)(Value & 0xDF); // clear the bit 
     } 
    } 
    public bool Bit6 
    { 
     get { return (Value & 0x40) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x40; 
      else 
       Value = (byte)(Value & 0xBF); // clear the bit 
     } 
    } 
    public bool Bit7 
    { 
     get { return (Value & 0x80) != 0; } 
     set 
     { 
      if (value) 
       Value |= 0x80; 
      else 
       Value = (byte)(Value & 0x7F); // clear the bit 
     } 
    } 

    public bool Set(bool value, int bitNo) 
    { 
     if (bitNo > 7 || bitNo < 0) 
      throw new ArgumentOutOfRangeException(); 

     if (value) 
      Value |= (byte)(0x01 << bitNo); 
     else 
      Value = (byte)(Value & ~(0x01 << bitNo)); // clear the bit 

     return value; 
    } 
    public bool Get(int bitNo) 
    { 
     if (bitNo > 7 || bitNo < 0) 
      throw new ArgumentOutOfRangeException(); 

     return ((Value >> bitNo) & 0x01) != 0; 
    } 
    public bool this[int bitNo] 
    { 
     get { return Get(bitNo); } 
     set { Set(value, bitNo); } 
    } 
} 
Các vấn đề liên quan