1

I know this question has been asked many times before, and I've tried to read through all the previous questions without much luck.

I am trying to convert the following C++ struct to C#, for use with socket communication.

enum class packet_type
{
    read_mem,
    get_base_addr,
    get_pid,
    completed
};

struct copy_mem
{
    unsigned int dest_process_id;
    unsigned long long dest_address;

    unsigned int src_process_id;
    unsigned long long src_address;

    unsigned int size;
};

struct get_base_addr
{
    unsigned int process_id;
};

struct get_pid
{
    size_t len;
    wchar_t name[256];
};

struct completed
{
    unsigned long long result;
};

struct PacketHeader
{
    //uint32_t   magic;
    packet_type type;
};

struct Packet
{
    PacketHeader header;
    union
    {
        copy_mem     copy_memory;
        get_base_addr get_base_address;
        get_pid get_pid;
        completed        completed;
    } data;
};

And this is my current C# implementation

public enum PacketType
{
    read_mem = 0,
    get_base_addr = 1,
    get_pid = 2,
    completed = 3
}

[StructLayout(LayoutKind.Sequential)]
public struct PacketHeader
{
    public PacketType type;
}

[StructLayout(LayoutKind.Sequential)]
public struct get_base_addr
{
    uint process_id;
};

[StructLayout(LayoutKind.Sequential)]
public struct get_pid
{
    public ulong len;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string name;
}

[StructLayout(LayoutKind.Sequential)]
public struct copy_mem
{
    public uint dest_process_id;
    public ulong dest_address;

    public uint src_process_id;
    public ulong src_address;
 
    public uint size;
}

[StructLayout(LayoutKind.Sequential)]
public struct completed
{
    public ulong result;
};

[StructLayout(LayoutKind.Explicit, Pack = 0, CharSet = CharSet.Unicode)]
public struct Packet
{
    [FieldOffset(0)] //
    public PacketHeader header;

    [FieldOffset(4)]
    public copy_mem CopyMem; //28

    [FieldOffset(32)]
    public get_base_addr GetBaseAddress;

    [FieldOffset(36)]
    public get_pid GetPid;

    [FieldOffset(300)]
    public completed Completed;
}

I am then using this method to convert the struct to a byte array for the socket transmission:

       public static byte[] RawSerialize(T item)
    {
        int rawSize = Marshal.SizeOf(typeof(T));
        IntPtr buffer = Marshal.AllocHGlobal(rawSize);
        var a = Marshal.SizeOf(item);
        var b = Marshal.SizeOf(buffer);

        Marshal.StructureToPtr(item, buffer, false);
        byte[] rawData = new byte[rawSize];
        Marshal.Copy(buffer, rawData, 0, rawSize);
        Marshal.FreeHGlobal(buffer);
        return rawData;
    }

The issue is that var a = Marshal.SizeOf(item); reports a size of 312, but the actual struct should be 528 bytes when I do sizeof(Packet) in C++

Sam
  • 602
  • 9
  • 21

1 Answers1

1

Your assumptions seem to be wrong. First of all, the wchar_t type may have different lengths on different machines. On mine, an x64 Linux box, it's 4 byte - that alone makes get_pid a 1032 byte sized struct. You might be interested in using a char16_t or char32_t type instead (see e.g. here).

Since the union in Packet overlaps all fields, this also makes Packet a 1040 byte-sized struct: 4 bytes for PacketHeader, 1032 bytes for get_pid - which is the "longest" struct in there by far - and 4 bytes for padding. Padding, sadly, is platform specific.

To get rid of padding from the C/C++ compiler, you'd need to use attributes such as GCC's __attribute__ ((packed)) or Visual C++'s #pragma pack(1) (see e.g. this SO answer).

Careful though, the field offsets in C# are wrong as well: Except for the header, all field offsets in Packet have to be [FieldOffset(4)] - since in C++ it's a union that starts at byte 4 (assuming zero padding).

For portability, also be aware that an unsigned long long is platform specific as well and that the only guarantee for it is to be at least 64 bit long. If you need exactly 64 bit, you may want to use uint64_t instead (see e.g. here).


Here's the code I used to determine sizes (Linux x64, GCC 9.3):

int main() {
    std::cout << "packet_type:   " << sizeof(packet_type) << std::endl;
    std::cout << "copy_mem:      " << sizeof(copy_mem) << std::endl;
    std::cout << "get_base_addr: " << sizeof(get_base_addr) << std::endl;
    std::cout << "get_pid:       " << sizeof(get_pid) << std::endl;
    std::cout << "completed:     " << sizeof(completed) << std::endl;
    std::cout << "PacketHeader:  " << sizeof(PacketHeader) << std::endl;
    std::cout << "Packet:        " << sizeof(Packet) << std::endl;
    std::cout << "wchar_t:       " << sizeof(wchar_t) << std::endl;
    return 0;
}

With padding (default structs):

packet_type:   4
copy_mem:      40
get_base_addr: 4
get_pid:       1032
completed:     8
PacketHeader:  4
Packet:        1040
wchar_t:       4

No padding (__attribute__ ((packed))):

packet_type:   4
copy_mem:      28
get_base_addr: 4
get_pid:       1032
completed:     8
PacketHeader:  4
Packet:        1036
wchar_t:       4

As was pointed out in the comments, setting the Packet struct's GetPid field to [FieldAlign(4)] will result in the following runtime error:

Unhandled exception. System.TypeLoadException: Could not load type 'Packet' from assembly 'StructSize, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field.

One way to work around this is to define the get_pid struct like so:

[StructLayout(LayoutKind.Sequential, Pack = 0)]
public unsafe struct get_pid
{
    public ulong len;
    public fixed byte name[256];
}

This is still assuming that the name string is 128 characters long, each of which is a 2-byte Unicode. In doing so, the name property is now of type byte*. To get back the string, the following two methods should work:

public static unsafe string GetName(get_pid gp) => 
    new string((sbyte*) gp.name, 0, 256, Encoding.Unicode);

public static unsafe string GetName(get_pid gp) =>
    Marshal.PtrToStringUni(new IntPtr(gp.name), 256);
sunside
  • 8,069
  • 9
  • 51
  • 74
  • Thank you for your detailed response. If I change the `FieldOffset` for all structs in `Packet`, I get this when I run the app - `Packet is invalid .... because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field.' ` – Sam Jul 08 '20 at 22:28
  • The culprit is the `get_pid` struct's `name` field, this much I can tell. – sunside Jul 08 '20 at 23:06