Reinterpret Array of Bytes into Managed Struct Using Fixed Buffers

谁说胖子不能爱 提交于 2021-01-29 02:30:18

问题


I'm looking to reinterpret_cast an array of bytes into a C# struct. I've read several other answers to the problem, most have been about how to implement reinterpret cast. I've settled on a means to reinterpret cast, but I'm getting single characters instead of arrays of characters during my casting.

For instance, I have the following object:

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

For some reason instead of having an array of bytes, I have single characters where an array should be. Why is this so? Here is my Locals debug window:

As you can see, it is considering all fields as char and not char[] for some reason.

If this is not the proper approach, is there something else I should be looking at? I've been looking into Span<T>.

EDIT: After further discussions with the author of the selected answer, Oguz Ozgul, it was determined that marshalling would be the best approach. A follow up question is, how would I deal with a nested struct? The following is my current approach. As Oguz mentioned, for structs defined outside of a class, and which contain primitive types, it is okay to exclude the Marshal attributes. These structs can then be used as fields in another struct. I've tackled defining nested structs similar to how I'd define non-nested structs.

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

回答1:


That is because only the first elements of your char arrays are set and the rest is zero (you can see this in the Memory window).

First of all, trying to fill the char array with raw binary data can cause undesired and unpredictable results, unless we can specify the exact charset. You can see on top of the struct that the CharSet is sete to Ansi, 1 bytes per character.

Then, instead of using fixed pointer char arrays, you can use strings but marshal them by value and with an exact size.

Please let me know if this helps:

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

UPDATE 1: The OP also asked an additional question in a comment below, so the answer is updated.

The OP wants to know what if another struct, which has an Int64 field, is embedded into the current one.

First, reading: https://docs.microsoft.com/en-us/dotnet/framework/interop/default-marshaling-behavior#default-marshaling-for-value-types

So I added this new struct to the source code:

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

Then, embedded it in the current one:

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

And finally, marshaled it back from unmanaged memory:

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


来源:https://stackoverflow.com/questions/60713048/reinterpret-array-of-bytes-into-managed-struct-using-fixed-buffers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!