使用固定缓冲区将字节数组重新解释为托管结构
Reinterpret Array of Bytes into Managed Struct Using Fixed Buffers
我正在寻找 reinterpret_cast 一个字节数组到 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];
}
出于某种原因,我没有字节数组,而是在应该是数组的地方有单个字符。为什么会这样?这是我的本地调试 window:
如您所见,出于某种原因,它正在将所有字段视为 char
而不是 char[]
。
如果这不是正确的方法,还有什么我应该看的吗?我一直在研究 Span<T>
.
编辑: 在与所选答案的作者 Oguz Ozgul 进一步讨论后,确定编组将是最佳方法。后续问题是,我将如何处理嵌套结构?以下是我目前的做法。正如 Oguz 提到的,对于在 class 之外定义的结构,并且包含原始类型,可以排除元帅属性。然后可以将这些结构用作另一个结构中的字段。我已经解决了定义嵌套结构的问题,类似于我定义非嵌套结构的方式。
[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 数组的第一个元素,其余元素为零(您可以在内存 window 中看到这一点)。
首先,尝试用原始二进制数据填充 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 字段的结构嵌入到当前结构中会怎样。
所以我在源代码中添加了这个新结构:
[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
});
我正在寻找 reinterpret_cast 一个字节数组到 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];
}
出于某种原因,我没有字节数组,而是在应该是数组的地方有单个字符。为什么会这样?这是我的本地调试 window:
如您所见,出于某种原因,它正在将所有字段视为 char
而不是 char[]
。
如果这不是正确的方法,还有什么我应该看的吗?我一直在研究 Span<T>
.
编辑: 在与所选答案的作者 Oguz Ozgul 进一步讨论后,确定编组将是最佳方法。后续问题是,我将如何处理嵌套结构?以下是我目前的做法。正如 Oguz 提到的,对于在 class 之外定义的结构,并且包含原始类型,可以排除元帅属性。然后可以将这些结构用作另一个结构中的字段。我已经解决了定义嵌套结构的问题,类似于我定义非嵌套结构的方式。
[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 数组的第一个元素,其余元素为零(您可以在内存 window 中看到这一点)。
首先,尝试用原始二进制数据填充 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 字段的结构嵌入到当前结构中会怎样。
所以我在源代码中添加了这个新结构:
[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
});