【问题标题】:Reinterpret Array of Bytes into Managed Struct Using Fixed Buffers使用固定缓冲区将字节数组重新解释为托管结构
【发布时间】:2020-03-18 01:36:31
【问题描述】:

我希望将字节数组重新解释为 C# 结构。我已经阅读了该问题的其他几个答案,大多数都是关于如何实现重新解释演员表。我已经确定了一种重新解释演员表的方法,但是在我的演员表中我得到的是单个字符而不是字符数组。

例如,我有以下对象:

    public unsafe struct Establish503
    {
        public static Establish503 ReinterpretCast(byte[] message)
        {
            GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned);
            Establish503 theStruct = (Establish503)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),
                typeof(Establish503));
            handle.Free();
            return theStruct;
        }

        public fixed char HMACSignature[32];
        public fixed char AccessKey[20];
        public fixed char TradingSystemName[30];
        public fixed char TradingSystemVersion[10];
        public fixed char TradingSystemVendor[10];
    }

由于某种原因,我没有一个字节数组,而是在一个数组应该有的地方有单个字符。为什么会这样?这是我的本地调试窗口:

如您所见,出于某种原因,它将所有字段视为char 而不是char[]

如果这不是正确的方法,我还有什么需要注意的吗?我一直在调查Span<T>

编辑:在与所选答案的作者 Oguz Ozgul 进一步讨论后,确定编组是最好的方法。一个后续问题是,我将如何处理嵌套结构?以下是我目前的做法。正如 Oguz 所提到的,对于在类之外定义且包含原始类型的结构,可以排除 Marshal 属性。然后这些结构可以用作另一个结构中的字段。我已经解决了定义嵌套结构的问题,类似于我定义非嵌套结构的方式。

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct OrderMassActionReport558
    {
        public const int templateId_ = 558;
        public const int blockSize_ = 103;

        public static OrderMassActionReport558 ReinterpretCast(byte[] message)
        {
            GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned);
            OrderMassActionReport558 theStruct = (OrderMassActionReport558)
                Marshal.PtrToStructure(handle.AddrOfPinnedObject(), 
                typeof(OrderMassActionReport558));
            handle.Free();
            return theStruct;
        }

        [MarshalAs(UnmanagedType.U4)]
        public uInt32 seqNum;
        [MarshalAs(UnmanagedType.U8)]
        public uInt64 uUID;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
        private byte[] _senderID;
        public string senderID => System.Text.Encoding.ASCII.GetString(this._senderID);
        [MarshalAs(UnmanagedType.U8)]
        public uInt64 partyDetailsListReqID;
        [MarshalAs(UnmanagedType.U8)]
        public uInt64 transactTime;
        [MarshalAs(UnmanagedType.U8)]
        public uInt64 sendingTimeEpoch;
        [MarshalAs(UnmanagedType.U8)]
        public uInt64 orderRequestID;
        [MarshalAs(UnmanagedType.U8)]
        public uInt64 massActionReportID;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
        private byte[] _securityGroup;
        public string securityGroup => System.Text.Encoding.ASCII.GetString(this._securityGroup);
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        private byte[] _location;
        public string location => System.Text.Encoding.ASCII.GetString(this._location);
        [MarshalAs(UnmanagedType.I4)]
        public Int32NULL securityID;
        [MarshalAs(UnmanagedType.U2)]
        public uInt16NULL delayDuration;
        [MarshalAs(UnmanagedType.U1)]
        public MassActionResponse massActionResponse;
        [MarshalAs(UnmanagedType.U1)]
        public ManualOrdIndReq manualOrderIndicator;
        [MarshalAs(UnmanagedType.U1)]
        public MassActionScope massActionScope;
        [MarshalAs(UnmanagedType.U1)]
        public uInt8 totalAffectedOrders;
        [MarshalAs(UnmanagedType.U1)]
        public BooleanFlag lastFragment;
        [MarshalAs(UnmanagedType.U1)]
        public uInt8NULL massActionRejectReason;
        [MarshalAs(UnmanagedType.U1)]
        public uInt8NULL marketSegmentID;
        [MarshalAs(UnmanagedType.U1)]
        public MassCxlReqTyp massCancelRequestType;
        [MarshalAs(UnmanagedType.U1)]
        public SideNULL side;
        [MarshalAs(UnmanagedType.U1)]
        public MassActionOrdTyp ordType;
        [MarshalAs(UnmanagedType.U1)]
        public MassCancelTIF timeInForce;
        [MarshalAs(UnmanagedType.U1)]
        public SplitMsg splitMsg;
        [MarshalAs(UnmanagedType.U1)]
        public BooleanNULL liquidityFlag;
        [MarshalAs(UnmanagedType.U1)]
        public BooleanFlag possRetransFlag;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct NoAffectedOrdersEntry
        {
            public const int blockSize_ = 32;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            private byte[] _origCIOrdID;
            public string origCIOrdID => System.Text.Encoding.ASCII.GetString(this._origCIOrdID);
            public uInt64 AffectedOrderID;
            public uInt32 CxlQuantity;
        }
    }

【问题讨论】:

  • 固定缓冲区字段作为指针处理,因此调试器将它们显示为char* 字段:它仅显示第一个字符。但是非空指针是可索引的,因此您可以访问所有字符,例如。 theStruct.HMACSignature[1]
  • 亲爱的@GyörgyKőszeg,据我所知,调试器将 char* 指针显示为 \0 终止的字符串。当我运行 OP 的代码并中断并在内存窗口中查看结构时,我看到只有每个 char* 变量的第一个字节被设置,其余的设置为零。这就是调试器只显示一个字符的原因,因为 char[] 或 char* 只包含一个字符。
  • 不是 C# 调试器:它将指针始终显示为可扩展的树节点。展开后,它会显示基础类型的 ToString 值。对于char*,它是单个字符。如果它们显示为以零结尾的字符串,则屏幕截图中的最后一项将是一个空字符串,而不是单个 \0 字符(另请参阅最后一列:它显示 char,而不是字符串)。跨度>

标签: c# byte buffer reinterpret-cast


【解决方案1】:

这是因为只设置了 char 数组的第一个元素,其余元素为零(您可以在“内存”窗口中看到这一点)。

首先,尝试用原始二进制数据填充 char 数组会导致不希望的和不可预测的结果,除非我们可以指定确切的字符集。您可以在结构顶部看到 CharSet 设置为 Ansi,每个字符 1 个字节。

然后,您可以使用字符串而不是使用固定指针 char 数组,但按值和精确大小对它们进行编组。

如果这有帮助,请告诉我:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public unsafe struct Establish503
    {
        public static Establish503 ReinterpretCast(byte[] message)
        {
            GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned);
            Establish503 theStruct = (Establish503)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),
                typeof(Establish503));
            handle.Free();
            return theStruct;
        }

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string HMACSignature;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
        public string AccessKey;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
        public string TradingSystemName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string TradingSystemVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string TradingSystemVendor;
    }  

更新 1:OP 还在下面的评论中提出了一个附加问题,因此更新了答案。

OP 想知道如果另一个具有 Int64 字段的结构嵌入到当前结构中会怎样。

首先,阅读:https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-behavior#default-marshaling-for-value-types

所以我在源代码中添加了这个新结构:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct Data
    {
        [MarshalAs(UnmanagedType.I8)]
        public long LongField;
    }

然后,将其嵌入到当前:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public unsafe struct Establish503
    {
        public static Establish503 ReinterpretCast(byte[] message)
        {
            GCHandle handle = GCHandle.Alloc(message, GCHandleType.Pinned);
            Establish503 theStruct = (Establish503)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),
                typeof(Establish503));
            handle.Free();
            return theStruct;
        }

        public Data DataStruct;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string HMACSignature;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
        public string AccessKey;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
        public string TradingSystemName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string TradingSystemVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
        public string TradingSystemVendor;
    }

最后,将它从非托管内存中封送回来:

        Establish503 establish503 = Establish503.ReinterpretCast(new byte[] { 
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
        });

【讨论】:

  • 我不确定它在哪里说字符集是 Ansi。在我的 Locals 窗口中,还是在我的代码 sn-p 中?我也看不到“Ansi”。感谢上帝,我读懂了 Pack=1 的含义。为了记录,这很好用。
  • Ansi 在这里,在我的回答中类顶部的 StructLayout 属性中:[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
  • 另外,我将如何使用自定义类型执行此操作? @OguzOzgul
  • 嗨。我们可能有很大的时间差异。现在是 09:39 :) 自定义类型是什么意思?有一个自定义类类型的字段而不是字符串?
  • 正确!例如,如果我创建一个名为 Data 的结构,并将字段 Data _myData 添加到 Establish503。就目前而言,我所做的是将编组添加到仅包含原始类型的数据中,类似于您向我展示的建立 503 的方式。例如,如果Data 包含Int64,我将在此字段上方添加[MarshalAs(UnmanagedType.I8)]。然后我在Establish503 中的Data _myData 上方添加了[MarshalAs(UnmanagedType.Struct)]。这是正确的做法吗?
猜你喜欢
  • 1970-01-01
  • 2011-09-14
  • 2016-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-12
相关资源
最近更新 更多