Đọc cấu trúc dữ liệu C / C ++ trong C # từ một mảng byte


85

Cách tốt nhất để điền vào một cấu trúc C # từ một mảng byte [] trong đó dữ liệu từ một cấu trúc C / C ++ là gì? Cấu trúc C sẽ trông giống như thế này (C của tôi rất gỉ):

typedef OldStuff {
    CHAR Name[8];
    UInt32 User;
    CHAR Location[8];
    UInt32 TimeStamp;
    UInt32 Sequence;
    CHAR Tracking[16];
    CHAR Filler[12];
}

Và sẽ điền một cái gì đó như thế này:

[StructLayout(LayoutKind.Explicit, Size = 56, Pack = 1)]
public struct NewStuff
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    [FieldOffset(0)]
    public string Name;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(8)]
    public uint User;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    [FieldOffset(12)]
    public string Location;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(20)]
    public uint TimeStamp;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(24)]
    public uint Sequence;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    [FieldOffset(28)]
    public string Tracking;
}

Cách tốt nhất để sao chép OldStuffvào NewStuff, nếu OldStuffđược chuyển dưới dạng mảng byte []?

Tôi hiện đang làm một cái gì đó như sau, nhưng nó cảm thấy hơi rắc rối.

GCHandle handle;
NewStuff MyStuff;

int BufferSize = Marshal.SizeOf(typeof(NewStuff));
byte[] buff = new byte[BufferSize];

Array.Copy(SomeByteArray, 0, buff, 0, BufferSize);

handle = GCHandle.Alloc(buff, GCHandleType.Pinned);

MyStuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));

handle.Free();

Có cách nào tốt hơn để thực hiện điều này không?


Việc sử dụng BinaryReaderlớp có mang lại bất kỳ lợi ích nào về hiệu suất khi ghim bộ nhớ và sử dụng Marshal.PtrStructurekhông?


1
FYI, Nếu chương trình của bạn chạy trên nhiều máy khác nhau, bạn có thể cần phải xử lý endian nhỏ và lớn.
KPexEA 17/09/08

1
Làm thế nào bạn có thể xử lý điều đó ở cấp độ cấu trúc, tức là mà không cần phải đảo ngược từng byte cho từng giá trị trong cấu trúc?
Pat

Câu trả lời:


114

Từ những gì tôi có thể thấy trong bối cảnh đó, bạn không cần phải sao chép SomeByteArrayvào bộ đệm. Bạn chỉ cần lấy xử lý từ SomeByteArray, ghim nó, sao chép IntPtrdữ liệu bằng cách sử dụng PtrToStructurevà sau đó phát hành. Không cần một bản sao.

Đó sẽ là:

NewStuff ByteArrayToNewStuff(byte[] bytes)
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        NewStuff stuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));
    }
    finally
    {
        handle.Free();
    }
    return stuff;
}

Phiên bản chung:

T ByteArrayToStructure<T>(byte[] bytes) where T: struct 
{
    T stuff;
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        handle.Free();
    }
    return stuff;
}

Phiên bản đơn giản hơn (yêu cầu unsafechuyển đổi):

unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    fixed (byte* ptr = &bytes[0])
    {
        return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
    }
}

CS0411 Không thể suy ra các đối số kiểu cho phương thức 'ByteArrayToStructure <T> (byte [], int)' từ cách sử dụng. Hãy thử chỉ định rõ ràng các đối số kiểu. (Tôi đã thêm chỉ số int của mảng byte) vào nó.
SSpoke

Sẽ làm rò rỉ bộ nhớ khi có ngoại lệ. Xem: stackoverflow.com/a/41836532/184528 để có phiên bản an toàn hơn.
cdiggins

2
Kể từ ngày 4.5.1, có một phiên bản chung của PtrToStructure, vì vậy dòng thứ hai trong phiên bản chung, ở trên, có thể trở thành: var stuff = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());

10

Đây là phiên bản an toàn ngoại lệ của câu trả lời được chấp nhận :

public static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try {
        return (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally {
        handle.Free();
    }
}

3
@ Ben-Collins Câu trả lời được chấp nhận đã được chỉnh sửa sau khi tôi thêm câu trả lời của mình.
cdiggins

5

Xem ra cho các vấn đề đóng gói. Trong ví dụ bạn đưa ra, tất cả các trường đều ở hiệu số rõ ràng bởi vì mọi thứ đều nằm trên ranh giới 4 byte nhưng điều này sẽ không luôn đúng như vậy. Visual C ++ đóng gói trên ranh giới 8 byte theo mặc định.


1
"Visual C ++ đóng gói trên ranh giới 8 byte theo mặc định." Điều này đã giải quyết vấn đề của tôi, cảm ơn rất nhiều!
Chris L

4
object ByteArrayToStructure(byte[] bytearray, object structureObj, int position)
{
    int length = Marshal.SizeOf(structureObj);
    IntPtr ptr = Marshal.AllocHGlobal(length);
    Marshal.Copy(bytearray, 0, ptr, length);
    structureObj = Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(bytearray, position), structureObj.GetType());
    Marshal.FreeHGlobal(ptr);
    return structureObj;
}   

Có cái này


0

Nếu bạn có một byte [], bạn sẽ có thể sử dụng lớp BinaryReader và đặt giá trị trên NewStuff bằng các phương thức ReadX có sẵn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.