【问题标题】:How to convert a structure that contains an array to a byte array?如何将包含数组的结构转换为字节数组?
【发布时间】:2018-04-05 09:55:41
【问题描述】:

如何在 C# 中将包含数组的结构转换为字节数组?

here 有一个关于没有数组的结构的问题。

但如果结构包含这样的数组:

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

将结构转换为字节时会导致访问冲突异常:

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

我的目标是使用 MSMQ 在消息队列中以字节为单位发送消息。

这里是编译和重现问题的完整代码。

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);
            */
        }
    }
}

【问题讨论】:

  • 只是好奇 - 为什么不让BinaryMessageFormatter 序列化呢?是为了让信息更小吗? MSMQ 消息的开销已经大于消息内容本身,所以对于这么小的东西,它不太可能产生太大的影响。
  • 如果我使用 BinaryMessageFormatter,它将数据存储为 340 字节而不是 16 字节。我想要尽可能小的数据包。这里的代码显示了这一点: // 打开或创建消息队列 if (!MessageQueue.Exists(queuePath)) MessageQueue.Create(queuePath); // 打开队列 var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()}; var message = new Message(packet, new BinaryMessageFormatter()); q.Send(message);
  • 简而言之,我想要尽可能小的数据包,因为我想要存储大量的数字和模拟点。这个例子很小,只是为了简单。
  • 不回答这个问题,但我建议只使用更紧凑的序列化协议(但不是Marshal 的东西)。例如,您可以使用 protobuf 将带有一个 DiObject 的数据包序列化为 14 个字节,这还不错。
  • 让 Google Protocol Buffers 为您完成繁重的工作。它是标准的、被广泛采用的,并且完全能够以最少的代码要求解决您的问题(例如,您只需定义一个 .proto 合同文件)。

标签: c# arrays struct msmq


【解决方案1】:

问题在于对 C# 中如何表示结构的错误假设

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

public DiObject[] Di 成员的大小为numDi * 4 的假设不正确。代替该字段的是指向结构数组的指针。 Array 是 .NET 中的一个类,不包含在结构声明中。

要解决这个问题,可以使用固定数组。我了解设计背后的想法是获取可变长度数组,并在下一个代码清单中介绍。

此代码在执行期间不会引发 AccessViolationException:

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);

        }
    }
}

下面的代码为具有可变内部数组大小的 MyPacket 结构提供了到字节数组和字节数组的有效转换。实现通过使用不安全的指针算法来避免强制转换和边界检查。

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);

        }
    }
}

【讨论】:

  • 感谢您对阵列的清晰解释。您的解决方案完美运行,您完全理解我需要将 C++ 程序连接到队列。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-14
相关资源
最近更新 更多