简体   繁体   中英

How to convert a structure that contains an array to a byte array?

How do I convert a structure that contains an array to a byte array in C#?

There was a question here about a struct without array.

But if the struct contains an array like this:

public struct DiObject
{
    public byte Command; 
    public byte ErrorClass; 
    public byte Reserved; 
    public byte Flags; 
}

public struct MyPacket
{
    public uint ProtocolIdentifier; 
    public uint NumDi;    
    public DiObject[] Di; 
}

It results with an access violation exception when converting the struct in a byte:

private static byte[] GetBytes(MyPacket packet, int packetSize)
{
    var data = new byte[packetSize];
    var ptr = Marshal.AllocHGlobal(packetSize);

    // ==== Access violation exception occurs here ====
    Marshal.StructureToPtr(packet, ptr, true);

    Marshal.Copy(ptr, data, 0, packetSize);
    Marshal.FreeHGlobal(ptr);
    return data;
}

My goal is to send a message in bytes in a message queue with MSMQ.

Here the complete code that compiles and reproduce the problem.

using System;
//using System.IO;
//using System.Messaging;
using System.Runtime.InteropServices;

namespace StructToBytes
{
    // 4 bytes
    [Serializable]
    public struct DiObject
    {
        public byte Command; 
        public byte ErrorClass; 
        public byte Reserved; 
        public byte Flags; 
    }

    // 8 + (numDi*4) bytes
    [Serializable]
    public struct MyPacket
    {
        public uint ProtocolIdentifier; 
        public uint NumDi;    
        public DiObject[] Di; 
    }

    internal class Program
    {
        private static byte[] GetBytes(MyPacket packet, int packetSize)
        {
            var data = new byte[packetSize];
            var ptr = Marshal.AllocHGlobal(packetSize);

            // ==== Access violation exception occurs here ====
            Marshal.StructureToPtr(packet, ptr, true);

            Marshal.Copy(ptr, data, 0, packetSize);
            Marshal.FreeHGlobal(ptr);
            return data;
        }

        private static MyPacket FromBytes(byte[] data)
        {
            var packet = new MyPacket();
            var dataSize = Marshal.SizeOf(packet);
            var ptr = Marshal.AllocHGlobal(dataSize);
            Marshal.Copy(data, 0, ptr, dataSize);
            packet = (MyPacket) Marshal.PtrToStructure(ptr, packet.GetType());
            Marshal.FreeHGlobal(ptr);
            return packet;
        }

        private static void Main(string[] args)
        {
            const string queuePath = @".\private$\test_msmq";

            // Create the packet
            var packet = new MyPacket();

            // 8 bytes
            packet.ProtocolIdentifier = 1;
            packet.NumDi = 2;

            // 8 bytes
            packet.Di = new DiObject[packet.NumDi];
            packet.Di[0].Command = 2;
            packet.Di[0].ErrorClass = 3;
            packet.Di[0].Flags = 4;
            packet.Di[0].Reserved = 5;
            packet.Di[1].Command = 6;
            packet.Di[1].ErrorClass = 7;
            packet.Di[1].Flags = 8;
            packet.Di[1].Reserved = 9;

            // Convert the struct in bytes
            const int packetSize = 16;
            var packetBytes = GetBytes(packet, packetSize);

            // Create the message
            /*
            var msg = new Message();
            msg.BodyStream = new MemoryStream(packetBytes);

            // Open or create the message queue
            if (!MessageQueue.Exists(queuePath))
                MessageQueue.Create(queuePath);

            // Open the queue
            var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};

            // Send the message to the queue
            q.Send(msg);
            */
        }
    }
}

The problem lies with wrong assumption about how structure is represented in C#

// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
    public uint ProtocolIdentifier;
    public uint NumDi;
    public DiObject[] Di;
}

The assumption that size of public DiObject[] Di member is numDi * 4 is not true. In place of this field there is a pointer to the array of structures. Array is a class in .NET and is not included in place in structure declaration.

To solve this problem one can use fixed arrays. I understand that the idea behind the design is to get variable length array and it is presented in next code listing.

This code does not raise AccessViolationException during executin:

using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;

namespace StructToBytes
{
    // 4 bytes
    [Serializable]
    [StructLayout(LayoutKind.Explicit)]
    public unsafe struct DiObject
    {
        [FieldOffset(0)]
        public byte Command;

        [FieldOffset(1)]
        public byte ErrorClass;

        [FieldOffset(2)]
        public byte Reserved;

        [FieldOffset(3)]
        public byte Flags;
    }

    // 8 + (numDi*4) bytes
    [Serializable]
    public unsafe struct MyPacket
    {
        public uint ProtocolIdentifier;
        public uint NumDi;
        public fixed byte Di[2 * 4];
    }

    internal unsafe class Program
    {
        private static byte[] GetBytes(MyPacket packet, int packetSize)
        {
            var data = new byte[packetSize];
            var ptr = Marshal.AllocHGlobal(packetSize);

            // ==== Access violation exception occurs here ====
            Marshal.StructureToPtr(packet, ptr, true);

            Marshal.Copy(ptr, data, 0, packetSize);
            Marshal.FreeHGlobal(ptr);
            return data;
        }

        private static MyPacket FromBytes(byte[] data)
        {
            var packet = new MyPacket();
            var dataSize = Marshal.SizeOf(packet);
            var ptr = Marshal.AllocHGlobal(dataSize);
            Marshal.Copy(data, 0, ptr, dataSize);
            packet = (MyPacket)Marshal.PtrToStructure(ptr, packet.GetType());
            Marshal.FreeHGlobal(ptr);
            return packet;
        }

        private static void Main(string[] args)
        {
            const string queuePath = @".\private$\test_msmq";

            // Create the packet
            var packet = new MyPacket();

            // 8 bytes
            packet.ProtocolIdentifier = 1;
            packet.NumDi = 2;

            // 8 bytes
            // packet.Di = new DiObject[packet.NumDi];
            packet.Di[0] = 2;
            packet.Di[1] = 3;
            packet.Di[2] = 4;
            packet.Di[3] = 5;
            packet.Di[4] = 6;
            packet.Di[5] = 7;
            packet.Di[6] = 8;
            packet.Di[7] = 9;

            // Convert the struct in bytes
            int packetSize = Marshal.SizeOf<MyPacket>();
            var packetBytes = GetBytes(packet, packetSize);

            // Create the message

            var msg = new Message();
            msg.BodyStream = new MemoryStream(packetBytes);

            // Open or create the message queue
            if (!MessageQueue.Exists(queuePath))
                MessageQueue.Create(queuePath);

            // Open the queue
            var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};

            // Send the message to the queue
            q.Send(msg);

        }
    }
}

Code below provides efficient conversion to byte array and from byte array for MyPacket struct with variable internal array size. Implementation avoids casts and bounds checks by using unsafe pointer arithmetic.

using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;

namespace StructToBytes
{
    // 4 bytes
    [Serializable]
    [StructLayout(LayoutKind.Explicit)]
    public unsafe struct DiObject
    {
        [FieldOffset(0)]
        public byte Command;

        [FieldOffset(1)]
        public byte ErrorClass;

        [FieldOffset(2)]
        public byte Reserved;

        [FieldOffset(3)]
        public byte Flags;
    }

    [Serializable]
    public unsafe struct MyPacket
    {
        public uint ProtocolIdentifier;
        public uint NumDi;
        public DiObject[] Di;

        public byte[] ToBytes()
        {
            byte[] buffer = new byte[NumDi];

            fixed(DiObject* pDi = Di)
            fixed(byte* pBuff = buffer)
            {
                var pBuffDi = (DiObject*)pBuff;
                var pDiPtr = pDi;
                for (int i = 0; i < NumDi; i++)
                    *pBuffDi++ = *pDiPtr++;
            }
            return buffer;
        }

        public static MyPacket Create(byte[] buffer)
        {
            // argument checking code here

            var packet = new MyPacket();
            packet.ProtocolIdentifier = buffer[0];
            packet.NumDi = buffer[1];
            packet.Di = new DiObject[packet.NumDi];

            fixed (byte* pBuf = buffer)
            fixed (DiObject* pDi = packet.Di)
            {
                byte* pBufPtr = pBuf;
                pBufPtr += 2;
                var pBufDi = (DiObject*)pBufPtr;
                var pDiPtr = pDi;

                for (int i = 0; i < packet.NumDi; i++)
                    *pDiPtr++ = *pBufDi++;
            }

            return packet;
        }
    }

    internal unsafe class Program
    {

        private static void Main(string[] args)
        {
            const string queuePath = @".\private$\test_msmq";

            // Create the packet
            var packet = new MyPacket();

            // 8 bytes
            packet.ProtocolIdentifier = 1;
            packet.NumDi = 5;

            // 8 bytes
            packet.Di = new DiObject[packet.NumDi];
            packet.Di[0].Command = 2;
            packet.Di[0].ErrorClass = 3;
            packet.Di[0].Flags = 4;
            packet.Di[0].Reserved = 5;
            packet.Di[1].Command = 6;
            packet.Di[1].ErrorClass = 7;
            packet.Di[1].Flags = 8;
            packet.Di[1].Reserved = 9;
            packet.Di[2].Command = 6;
            packet.Di[2].ErrorClass = 7;
            packet.Di[2].Flags = 8;
            packet.Di[2].Reserved = 9;
            packet.Di[3].Command = 6;
            packet.Di[3].ErrorClass = 7;
            packet.Di[3].Flags = 8;
            packet.Di[3].Reserved = 9;

            // Create the message

            var msg = new Message();
            msg.BodyStream = new MemoryStream(packet.ToBytes());

            // Open or create the message queue
            if (!MessageQueue.Exists(queuePath))
                MessageQueue.Create(queuePath);

            // Open the queue
            var q = new MessageQueue(queuePath);

            // Send the message to the queue
            q.Send(msg);

        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM