Làm cách nào để chuyển đổi cấu trúc thành mảng byte trong C #?


83

Làm cách nào để chuyển đổi cấu trúc thành mảng byte trong C #?

Tôi đã xác định một cấu trúc như thế này:

public struct CIFSPacket
{
    public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
    public byte command;

    public byte errorClass;
    public byte reserved;
    public ushort error;

    public byte flags;

    //Here there are 14 bytes of data which is used differently among different dialects.
    //I do want the flags2. However, so I'll try parsing them.
    public ushort flags2;

    public ushort treeId;
    public ushort processId;
    public ushort userId;
    public ushort multiplexId;

    //Trans request
    public byte wordCount;//Count of parameter words defining the data portion of the packet.
    //From here it might be undefined...

    public int parametersStartIndex;

    public ushort byteCount; //Buffer length
    public int bufferStartIndex;

    public string Buffer;
}

Trong phương thức chính của tôi, tôi tạo một phiên bản của nó và gán các giá trị cho nó:

CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;

packet.Buffer = "NT LM 0.12";

Bây giờ tôi muốn gửi Packet này bằng socket. Đối với điều đó, tôi cần chuyển đổi cấu trúc thành một mảng byte. Tôi làm nó như thế nào?

Mã đầy đủ của tôi như sau.

static void Main(string[] args)
{

  Socket MyPing = new Socket(AddressFamily.InterNetwork,
  SocketType.Stream , ProtocolType.Unspecified ) ;


  MyPing.Connect("172.24.18.240", 139);

    //Fake an IP Address so I can send with SendTo
    IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
    IPEndPoint IPEP = new IPEndPoint(IP, 139);

    //Local IP for Receiving
    IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
    EndPoint EP = (EndPoint)Local;

    CIFSPacket packet = new CIFSPacket();
    packet.protocolIdentifier = 0xff;
    packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
    packet.errorClass = 0xff;
    packet.error = 0;
    packet.flags = 0x00;
    packet.flags2 = 0x0001;
    packet.multiplexId = 22;
    packet.wordCount = 0;
    packet.byteCount = 119;

    packet.Buffer = "NT LM 0.12";

    MyPing.SendTo(It takes byte array as parameter);
}

Đoạn mã sẽ là gì?


Một lần sửa ở dòng cuối cùng MyPing.Send (Nó nhận mảng byte làm tham số); Nó được Gửi không SendTo ......
Swapnil Gupta

Hi Petar, tôi không giúp bạn có được ...
Swapnil Gupta

3
Có thể tốt nếu chấp nhận một số câu trả lời cho các câu hỏi trước đây của bạn.
jnoss,

1
Tôi nghi ngờ rằng nó sẽ hữu ích để cụ thể hơn một chút về đầu ra mà bạn mong đợi; có rất nhiều cách để biến nó thành một byte [] ... Chúng ta có thể đưa ra một số giả định về hầu hết nó, rằng bạn muốn các biểu diễn kích thước cố định theo thứ tự mạng-byte-thứ tự trường của các trường - nhưng còn chuỗi?
Marc Gravell

Hãy quan tâm đến Grand Endian và Little endian và khoảng 32 bit / 64 bit nếu bạn chọn tùy chọn Marshall.
x77,

Câu trả lời:


126

Điều này là khá dễ dàng, sử dụng marshalling.

Đầu tệp

using System.Runtime.InteropServices

Chức năng

byte[] getBytes(CIFSPacket str) {
    int size = Marshal.SizeOf(str);
    byte[] arr = new byte[size];

    IntPtr ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(str, ptr, true);
    Marshal.Copy(ptr, arr, 0, size);
    Marshal.FreeHGlobal(ptr);
    return arr;
}

Và để chuyển đổi nó trở lại:

CIFSPacket fromBytes(byte[] arr) {
    CIFSPacket str = new CIFSPacket();

    int size = Marshal.SizeOf(str);
    IntPtr ptr = Marshal.AllocHGlobal(size);

    Marshal.Copy(arr, 0, ptr, size);

    str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
    Marshal.FreeHGlobal(ptr);

    return str;
}

Trong cấu trúc của bạn, bạn sẽ cần đặt điều này trước một chuỗi

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;

Và đảm bảo SizeConst lớn bằng chuỗi lớn nhất có thể của bạn.

Và bạn có thể nên đọc cái này: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx


Cảm ơn Vincet. GetBytes () nên được gọi sau khi gửi byte [] ?? và phương thức frombytes () đang gửi các byte? Tôi hơi bối rối bạn thân?
Swapnil Gupta

1
GetBytes chuyển đổi từ cấu trúc của bạn thành một mảng. FromBytes chuyển đổi từ các byte trở lại cấu trúc của bạn. Điều này được thể hiện rõ ràng từ các chữ ký chức năng.
Vincent McNabb,

1
@Swapnil Đó là một câu hỏi khác, bạn nên hỏi riêng. Bạn nên xem xét hoàn thành một vài hướng dẫn CE về ổ cắm. Chỉ cần tìm kiếm trên Google.
Vincent McNabb,

3
Trong phương thức fromBytes của bạn, không cần phải phân bổ CIFSPacket hai lần. Marshal.SizeOf sẽ vui vẻ lấy Type làm tham số và Marshal.PtrToStructure phân bổ một đối tượng được quản lý mới.
Jack Ukleja

1
Lưu ý rằng trong một số trường hợp, hàm «StructureToPtr» ném một ngoại lệ. Điều này có thể được khắc phục bằng cách chuyển «false» thay vì «true» đến Marshal.StructureToPtr(str, ptr, false);. Nhưng cần phải đề cập đến mà tôi đang sử dụng các chức năng bao bọc cho một chung chung, mặc dù ...
Hi-Thiên thần

30

Nếu bạn thực sự muốn nó NHANH CHÓNG trên Windows, bạn có thể làm điều đó bằng cách sử dụng mã không an toàn với CopyMemory. CopyMemory nhanh hơn khoảng 5 lần (ví dụ: 800 MB dữ liệu mất 3 giây để sao chép qua marshalling, trong khi chỉ mất 0,6 giây để sao chép qua CopyMemory). Phương pháp này không giới hạn bạn chỉ sử dụng dữ liệu thực sự được lưu trữ trong struct blob, ví dụ như số hoặc mảng byte có độ dài cố định.

    [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
    private static unsafe extern void CopyMemory(void *dest, void *src, int count);

    private static unsafe byte[] Serialize(TestStruct[] index)
    {
        var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
        fixed (void* d = &buffer[0])
        {
            fixed (void* s = &index[0])
            {
                CopyMemory(d, s, buffer.Length);
            }
        }

        return buffer;
    }

4
Xin gửi lời chào tới những người đang đọc câu trả lời này .. Đây không phải là nền tảng thân thiện với Cross (nó chỉ sử dụng kernel32.dll của Windows). Nhưng sau đó một lần nữa, nó được viết vào năm 2014. :)
Raid vào

24

Hãy xem các phương pháp sau:

byte [] StructureToByteArray(object obj)
{
    int len = Marshal.SizeOf(obj);

    byte [] arr = new byte[len];

    IntPtr ptr = Marshal.AllocHGlobal(len);

    Marshal.StructureToPtr(obj, ptr, true);

    Marshal.Copy(ptr, arr, 0, len);

    Marshal.FreeHGlobal(ptr);

    return arr;
}

void ByteArrayToStructure(byte [] bytearray, ref object obj)
{
    int len = Marshal.SizeOf(obj);

    IntPtr i = Marshal.AllocHGlobal(len);

    Marshal.Copy(bytearray,0, i,len);

    obj = Marshal.PtrToStructure(i, obj.GetType());

    Marshal.FreeHGlobal(i);
}

Đây là bản sao đáng xấu hổ của một chủ đề khác mà tôi đã tìm thấy trên Google!

Cập nhật : Để biết thêm chi tiết, hãy kiểm tra nguồn


Tôi có Cấu trúc chuyển đổi sang mảng byte bằng cách sử dụng Marshalling bây giờ làm cách nào để kiểm tra xem tôi có nhận được phản hồi từ socket không? Làm thế nào để kiểm tra điều đó?
Swapnil Gupta,

@Alastair, tôi đã bỏ lỡ điều đó !! Cảm ơn vì đã chỉ nó .. Tôi đã cập nhật câu trả lời của mình.
Abdel Raoof

2
Tùy chọn này phụ thuộc vào nền tảng - Hãy quan tâm đến Grand Endian và Little endian và khoảng 32 Bit / 64 bit.
x77

@Abdel, và -1 đã biến mất :)
Alastair Pitts,

Có hợp lý không khi thực hiện Alloc, quấn thử bit ở giữa và sau đó đặt Free bên trong cuối cùng? Có vẻ như mọi thứ sẽ không thành công, nhưng nếu chúng xảy ra, liệu bộ nhớ có bao giờ được giải phóng không?
Casey

17

Biến thể của mã Vicent với một cấp phát bộ nhớ ít hơn:

public static byte[] GetBytes<T>(T str)
{
    int size = Marshal.SizeOf(str);

    byte[] arr = new byte[size];

    GCHandle h = default(GCHandle);

    try
    {
        h = GCHandle.Alloc(arr, GCHandleType.Pinned);

        Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return arr;
}

public static T FromBytes<T>(byte[] arr) where T : struct
{
    T str = default(T);

    GCHandle h = default(GCHandle);

    try
    {
        h = GCHandle.Alloc(arr, GCHandleType.Pinned);

        str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());

    }
    finally
    {
        if (h.IsAllocated)
        {
            h.Free();
        }
    }

    return str;
}

Tôi sử dụng GCHandleđể "ghim" bộ nhớ và sau đó tôi sử dụng trực tiếp địa chỉ của nó với h.AddrOfPinnedObject().


Nên loại bỏ where T : structnếu không sẽ phàn nàn về việc Tbị pass không a non-nullable type.
codenamezero

GCHandle.Allocsẽ không thành công nếu cấu trúc có dữ liệu không phải blittable, ví dụ như một mảng
joe

@joe Bạn nói đúng. Mã được viết cho cấu trúc đã cho, chỉ chứa các loại blittable và string.
xanatos

5

Vì câu trả lời chính là sử dụng loại CIFSPacket, không (hoặc không còn) khả dụng trong C #, tôi đã viết các phương thức chính xác:

    static byte[] getBytes(object str)
    {
        int size = Marshal.SizeOf(str);
        byte[] arr = new byte[size];
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(str, ptr, true);
        Marshal.Copy(ptr, arr, 0, size);
        Marshal.FreeHGlobal(ptr);

        return arr;
    }

    static T fromBytes<T>(byte[] arr)
    {
        T str = default(T);

        int size = Marshal.SizeOf(str);
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(arr, 0, ptr, size);

        str = (T)Marshal.PtrToStructure(ptr, str.GetType());
        Marshal.FreeHGlobal(ptr);

        return str;
    }

Đã kiểm tra, chúng hoạt động.


3

Tôi biết điều này thực sự đã muộn, nhưng với C # 7.3, bạn có thể thực hiện việc này đối với các cấu trúc không được quản lý hoặc bất kỳ thứ gì khác không bị thay đổi (int, bool, v.v.):

public static unsafe byte[] ConvertToBytes<T>(T value) where T : unmanaged {
        byte* pointer = (byte*)&value;

        byte[] bytes = new byte[sizeof(T)];
        for (int i = 0; i < sizeof(T); i++) {
            bytes[i] = pointer[i];
        }

        return bytes;
    }

Sau đó sử dụng như thế này:

struct MyStruct {
        public int Value1;
        public int Value2;
        //.. blah blah blah
    }

    byte[] bytes = ConvertToBytes(new MyStruct());

2

Bạn có thể sử dụng Marshal (StructureToPtr, ptrToStructure) và Marshal.copy nhưng điều này phụ thuộc vào plataform.


Serialization bao gồm các Chức năng để Tùy chỉnh Serialization.

public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

SerializationInfo bao gồm các hàm để tuần tự hóa từng thành viên.


BinaryWriter và BinaryReader cũng chứa các phương thức để Lưu / Tải vào Mảng Byte (Luồng).

Lưu ý rằng bạn có thể tạo Dòng nhớ từ Mảng Byte hoặc Mảng Byte từ Dòng nhớ.

Bạn có thể tạo một phương thức Lưu và một phương thức Mới trên cấu trúc của mình:

   Save(Bw as BinaryWriter)
   New (Br as BinaryReader)

Sau đó bạn chọn thành viên Lưu / Nạp vào Stream -> Byte Array.


1

Điều này có thể được thực hiện rất đơn giản.

Xác định cấu trúc của bạn một cách rõ ràng với [StructLayout(LayoutKind.Explicit)]

int size = list.GetLength(0);
IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
DataStruct *ptrBuffer = (DataStruct*)addr;
foreach (DataStruct ds in list)
{
    *ptrBuffer = ds;
    ptrBuffer += 1;
}

Mã này chỉ có thể được viết trong một ngữ cảnh không an toàn. Bạn phải giải phóng addrkhi bạn hoàn thành nó.

Marshal.FreeHGlobal(addr);

Khi thực hiện các thao tác có thứ tự rõ ràng trên một tập hợp kích thước cố định, bạn có thể nên sử dụng một mảng và vòng lặp for. Mảng vì nó có kích thước cố định và vòng lặp for bởi vì foreach không được đảm bảo theo thứ tự bạn mong đợi, trừ khi bạn biết cách triển khai cơ bản của loại danh sách và kiểu liệt kê của bạn và nó sẽ không bao giờ thay đổi. Ví dụ, người ta có thể xác định người điều tra bắt đầu từ cuối và lùi lại.

1

Tôi đã nghĩ ra một cách tiếp cận khác có thể chuyển đổi bất kỳ struct mà không gặp rắc rối trong việc sửa độ dài, tuy nhiên mảng byte kết quả sẽ có nhiều chi phí hơn một chút.

Đây là một mẫu struct:

[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
    public MyEnum enumvalue;
    public string reqtimestamp;
    public string resptimestamp;
    public string message;
    public byte[] rawresp;
}

Như bạn có thể thấy, tất cả các cấu trúc đó sẽ yêu cầu thêm các thuộc tính độ dài cố định. Điều này thường có thể chiếm nhiều dung lượng hơn yêu cầu. Lưu ý rằng điều LayoutKind.Sequentialnày là bắt buộc, vì chúng ta muốn sự phản chiếu luôn cung cấp cho chúng ta cùng một thứ tự khi kéo FieldInfo. Cảm hứng của tôi là từ TLVLoại-Độ dài-Giá trị. Hãy xem mã:

public static byte[] StructToByteArray<T>(T obj)
{
    using (MemoryStream ms = new MemoryStream())
    {
        FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (FieldInfo info in infos)
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream inms = new MemoryStream()) {

                bf.Serialize(inms, info.GetValue(obj));
                byte[] ba = inms.ToArray();
                // for length
                ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));

                // for value
                ms.Write(ba, 0, ba.Length);
            }
        }

        return ms.ToArray();
    }
}

Hàm trên chỉ đơn giản sử dụng BinaryFormatter để tuần tự hóa kích thước thô chưa biết objectvà tôi chỉ cần theo dõi kích thước và lưu trữ nó bên trong đầu ra MemoryStream.

public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
    output = (T) Activator.CreateInstance(typeof(T), null);
    using (MemoryStream ms = new MemoryStream(data))
    {
        byte[] ba = null;
        FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (FieldInfo info in infos)
        {
            // for length
            ba = new byte[sizeof(int)];
            ms.Read(ba, 0, sizeof(int));

            // for value
            int sz = BitConverter.ToInt32(ba, 0);
            ba = new byte[sz];
            ms.Read(ba, 0, sz);

            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream inms = new MemoryStream(ba))
            {
                info.SetValue(output, bf.Deserialize(inms));
            }
        }
    }
}

Khi chúng tôi muốn chuyển đổi nó trở lại ban đầu, structchúng tôi chỉ cần đọc độ dài trở lại và trực tiếp kết xuất nó trở lại và sau BinaryFormatterđó đổ ngược lại vào struct.

2 chức năng này là chung và nên hoạt động với bất kỳ struct , tôi đã thử nghiệm đoạn mã trên trong C#dự án của mình , nơi tôi có máy chủ và máy khách, được kết nối và giao tiếp qua NamedPipeStreamvà tôi chuyển tiếp structmảng byte dưới dạng của mình từ cái này sang cái khác và chuyển đổi nó trở lại .

Tôi tin rằng cách tiếp cận của tôi có thể tốt hơn, vì bản thân nó không cố định độ dài structvà chi phí duy nhất chỉ intdành cho mọi trường bạn có trong cấu trúc của mình. Ngoài ra còn có một số chi phí bit nhỏ bên trong mảng byte được tạo ra bởi BinaryFormatter, nhưng ngoài ra, không nhiều.


6
Nói chung, khi mọi người đang cố gắng giải quyết những thứ như vậy, họ cũng lo lắng về hiệu suất tuần tự hóa. Về lý thuyết, bất kỳ mảng cấu trúc nào cũng có thể được diễn giải lại dưới dạng mảng byte mà không cần tuần tự hóa và sao chép tốn kém.
Tanveer Badar


0

Có vẻ như cấu trúc (mức C) được xác định trước cho một số thư viện bên ngoài. Marshal là bạn của bạn. Kiểm tra:

http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx

cho người mới bắt đầu làm thế nào để đối phó với điều này. Lưu ý rằng bạn có thể - với các thuộc tính - xác định những thứ như bố cục byte và xử lý chuỗi. Cách tiếp cận RẤT tốt, thực sự.

Cả BinaryFormatter và MemoryStream đều không được thực hiện cho điều đó.


0

@Abdel Olakara answer donese không hoạt động trong .net 3.5, nên được sửa đổi như sau:

    public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
    {
        int len = Marshal.SizeOf(obj);
        IntPtr i = Marshal.AllocHGlobal(len);
        Marshal.Copy(bytearray, 0, i, len);
        obj = (T)Marshal.PtrToStructure(i, typeof(T));
        Marshal.FreeHGlobal(i);
    }

0
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

Điều này sẽ thực hiện thủ thuật một cách nhanh chóng, phải không?


Phiên bản GCHandle tốt hơn nhiều.
Петър Петров

0

Ví dụ này ở đây chỉ áp dụng cho các loại blittable thuần túy, ví dụ: các loại có thể được ghi nhớ trực tiếp trong C.

Ví dụ - cấu trúc 64-bit nổi tiếng

[StructLayout(LayoutKind.Sequential)]  
public struct Voxel
{
    public ushort m_id;
    public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}

Được định nghĩa chính xác như thế này, cấu trúc sẽ tự động được đóng gói dưới dạng 64-bit.

Bây giờ chúng ta có thể tạo khối lượng voxels:

Voxel[,,] voxels = new Voxel[16,16,16];

Và lưu tất cả chúng vào một mảng byte:

int size = voxels.Length * 8; // Well known size: 64 bits
byte[] saved = new byte[size];
GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
h.Free();
// now feel free to save 'saved' to a File / memory stream.

Tuy nhiên, vì OP muốn biết cách tự chuyển đổi cấu trúc, cấu trúc Voxel của chúng ta có thể có phương thức sau ToBytes:

byte[] bytes = new byte[8]; // Well known size: 64 bits
GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
h.Free();
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.