Имитируйте вложенные структуры C ++ с объединением в C# - PullRequest
1 голос
/ 09 июля 2020

Я знаю, что этот вопрос задавали много раз раньше, и я безуспешно пытался прочитать все предыдущие вопросы.

Я пытаюсь преобразовать следующую структуру C ++ в C#, для использования с сокетом связи.

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

И это моя текущая C# реализация

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

Затем я использую этот метод для преобразования структуры в байтовый массив для сокета передача:

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

Проблема в том, что var a = Marshal.SizeOf(item); сообщает размер 312, но фактическая структура должна быть 528 байтов, когда я делаю sizeof(Packet) в C ++

1 Ответ

1 голос
/ 09 июля 2020

Ваши предположения кажутся неверными. Во-первых, тип wchar_t может иметь разную длину на разных машинах. У меня блок x64 Linux, это 4 байта - только это дает структуру размером get_pid 1032 байта. Возможно, вам будет интересно использовать вместо этого тип char16_t или char32_t (см., Например, здесь ).

Поскольку union в Packet перекрывает все поля, это также делает Packet a 1040 структура размером в байт: 4 байтов для PacketHeader, 1032 байтов для get_pid - что на сегодняшний день является самой «длинной» структурой - и 4 байтов для заполнения. К сожалению, заполнение зависит от платформы c.

Чтобы избавиться от заполнения в компиляторе C / C ++, вам нужно будет использовать такие атрибуты, как G CC s __attribute__ ((packed)) или Visual C ++. #pragma pack(1) (см., Например, this SO answer).

Однако будьте осторожны, смещения полей в C# также неверны: за исключением заголовка, все смещения полей в Packet должно быть [FieldOffset(4)] - так как в C ++ это union, которое начинается с байта 4 (при нулевом заполнении).

Для переносимости также имейте в виду, что unsigned long long является платформой, c также и что единственная гарантия для него - быть как минимум 64 бит. Если вам нужно ровно 64 бит, вы можете вместо этого использовать uint64_t (см., Например, здесь ).

Вот код, который я использовал для определения размеров (Linux x64, G CC 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;
}

С заполнением (структуры по умолчанию):

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

Нет заполнение (__attribute__ ((packed))):

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

Как было указано в комментариях, установка поля Packet struct GetPid на [FieldAlign(4)] приведет к следующей ошибке времени выполнения:

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.

Один из способов обойти это - определите структуру get_pid следующим образом:

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

Это все еще предполагает, что строка имени имеет длину 128 символов, каждый из которых является 2-байтовым Unicode. При этом свойство name теперь имеет тип byte*. Чтобы вернуть строку, должны работать следующие два метода:

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...