Kosmo零
Kosmo零

Reputation: 4151

Why am I getting an access violation when trying to convert bytes array to class instance with Marshal.PtrToStructure?

In C++, I have this:

struct BasePacketProto
{
    unsigned short PACKET_OPCODE;
    unsigned short PACKET_MAGIC_NUMBER;
    unsigned short PACKET_REMAIN_DATA_LENGTH;
};

struct CM_Request_AcceptAccount : BasePacketProto
{
    unsigned char ACCOUNT_LOGIN[16];
    unsigned char ACCOUNT_PASSWORD[16];
};

static void SendPacket()
{
    CM_Request_AcceptAccount packet;
    packet.PACKET_OPCODE = opCM_Request_AcceptAccount;
    packet.PACKET_MAGIC_NUMBER = 123;
    packet.PACKET_REMAIN_DATA_LENGTH = sizeof(CM_Request_AcceptAccount) -
                                       sizeof(BasePacketProto);
    memcpy(packet.ACCOUNT_LOGIN, "asd", sizeof("asd") * sizeof(char));
    memcpy(packet.ACCOUNT_PASSWORD, "asd_pass", sizeof("asd_pass") * sizeof(char));

    //Send the packet to the server.
    int lLength = send(lhSocket, (const char*)&packet, sizeof(CM_Request_AcceptAccount), 0);
}

In C#, this:

[StructLayout(LayoutKind.Sequential)]
class BasePacketProto
{
    public System.UInt16 PACKET_OPCODE;
    public System.UInt16 PACKET_MAGIC_NUMBER;
    public System.UInt16 PACKET_REMAIN_DATA_LENGTH;
}

[StructLayout(LayoutKind.Sequential)]
class CM_Request_AcceptAccount : BasePacketProto
{
    public byte[] ACCOUNT_LOGIN = new byte[16];
    public byte[] ACCOUNT_PASSWORD = new byte[16];
}

Class that splits packets:

public class PacketProcessor
{
    static List<byte> raw_packet = new List<byte>();
    static int PACKET_HEADER_SIZE = Marshal.SizeOf(typeof(BasePacketProto));

    static public void ProcessPacketBytes(byte[] bytes, int size)
    {
        for (int i = 0; i < size; i++)
            raw_packet.Add(bytes[i]); //Adding bytes to own storage

        if (raw_packet.Count < PACKET_HEADER_SIZE) //If we don't have enough bytes
                                                   //to build base packet, we will
                                                   //return and wait for more.
            return;

        //This packet building works fine!
        BasePacketProto bpp =
            ConvertBytesTo<BasePacketProto>(raw_packet.GetRange(
                0, PACKET_HEADER_SIZE).ToArray());

        if (raw_packet.Count >= (PACKET_HEADER_SIZE +
                                 bpp.PACKET_REMAIN_DATA_LENGTH)) //If we have enough
                                           bytes in storage to restore child packet.
        {
            switch ((ClientPacketOpcodes)bpp.PACKET_OPCODE)
            {
                case ClientPacketOpcodes.opCM_Request_AcceptAccount:
                    //But this one fails
                    bpp = ConvertBytesTo<CM_Request_AcceptAccount>(raw_packet.GetRange(
                        0, PACKET_HEADER_SIZE + bpp.PACKET_REMAIN_DATA_LENGTH).ToArray());

                    PacketHandler.Handle_opCM_Request_AcceptAccount((CM_Request_AcceptAccount)bpp);
                    break;

                default:
                    break;
            }

            raw_packet.RemoveRange(0, PACKET_HEADER_SIZE + bpp.PACKET_REMAIN_DATA_LENGTH);
        }
    }

    static T ConvertBytesTo<T>(byte[] data)
    {
        unsafe
        {
            fixed(byte *ptr = data)
            {
                //I am getting an access violation here when trying to
                //build child packet :(
                return (T)Marshal.PtrToStructure(new IntPtr(ptr), typeof(T));
            }
        }
    }
}

Somewhere in the receive thread:

while (clientStream.CanRead)
{
    byte[] temp_buff = new byte[1024];
    int received = 0;

    while ((received = clientStream.Read(temp_buff, 0, 1024)) > 0) //Here we receive 38 bytes
    {
        //Passing it to the packet splitter.
        PacketProcessor.ProcessPacketBytes(temp_buff, received);
    }
}

Result when client send packet to server:

System.AccessViolationException was not handled
Attempt to read or write to protected memory. Most likely it points that other memory is corrupted.

Why does it fail to convert 38 bytes to CM_Request_AcceptAccount? What should I do to make it work?

Upvotes: 4

Views: 746

Answers (1)

Amit Mittal
Amit Mittal

Reputation: 2736

When you declare a struct like

struct CM_Request_AcceptAccount : BasePacketProto
{
    unsigned char ACCOUNT_LOGIN[16];
    unsigned char ACCOUNT_PASSWORD[16];
};

in C++, the array is 'inline' and of 'fixed' length or in other words contribute 16 bytes each to the 'size' of the struct.

But in C# you are re-declaring the same struct as:

[StructLayout(LayoutKind.Sequential)]
class CM_Request_AcceptAccount : BasePacketProto
{
    public byte[] ACCOUNT_LOGIN = new byte[16];
    public byte[] ACCOUNT_PASSWORD = new byte[16];
}

Here, you provide no information that the first 16 bytes belong to ACCOUNT_LOGIN array and the next 16 to ACCOUNT_PASSWORD.

The line byte[] ACCOUNT_LOGIN = new byte[16]

does not tell anything to the marshaller. It only causes the CLR to allocate a 16 byte array on the heap when an instance of CM_Request_AcceptAccount is created in the code.

In order to marshal the struct correctly, change the C# declaration to:

[StructLayout(LayoutKind.Sequential)]
class CM_Request_AcceptAccount : BasePacketProto
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
    public byte[] ACCOUNT_LOGIN;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
    public byte[] ACCOUNT_PASSWORD;
}

Additional information: Since your char arrays are meant to hold C-style string, you can also use

[MarshalAs(UnmanagedType.ByValTStr, SizeConst=16)]
public string ACCOUNT_LOGIN;

A point to remember here is that the marshaller will expect a null terminating character in the 16 characters you set in your C++ code.

Upvotes: 2

Related Questions