Маршал.PtrToStructure (и обратно) и общее решение для замены endianness

у меня есть система, где удаленный агент отправляет сериализованные структуры (из встроенной системы C) для чтения и хранения через IP/UDP. В некоторых случаях мне нужно отправить обратно те же типы структур. Я думал, что у меня хорошая установка с маршалом.PtrToStructure (получать) и Маршал.StructureToPtr (отправить). Однако небольшой gotcha заключается в том, что сетевые большие целые числа должны быть преобразованы в мой формат x86 little endian для локального использования. Когда я отправляю их снова, большой эндиан-это путь к идти.

вот вопрос:

    private static T BytesToStruct<T>(ref byte[] rawData) where T: struct
    {
        T result = default(T);
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));
        }
        finally
        {
            handle.Free();
        }
        return result;
    }

    private static byte[] StructToBytes<T>(T data) where T: struct
    {
        byte[] rawData = new byte[Marshal.SizeOf(data)];
        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr rawDataPtr = handle.AddrOfPinnedObject();
            Marshal.StructureToPtr(data, rawDataPtr, false);
        }
        finally
        {
            handle.Free();
        }
        return rawData;
    }

и быстрый пример структуры, которая может быть использована следующим образом:

byte[] data = this.sock.Receive(ref this.ipep);
Request request = BytesToStruct<Request>(ref data);

где структура, о которой идет речь, выглядит так:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct Request
{
    public byte type;
    public short sequence;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public byte[] address;
}

каким (общим) способом я могу поменять endianness при сортировке структур? Моя потребность такова, что локально сохраненный запрос.последовательность в этом примере должен быть обратным для отображения пользователю. Я не хочу, чтобы мне пришлось менять endianness в структурно-специфический способ, поскольку это общая проблема.

моей первой мыслью было использовать отражение, но я не очень хорошо знаком с этой особенностью. Кроме того, я надеялся, что там будет лучшее решение, на которое кто-то может указать мне. Заранее спасибо:)

4 ответов


отражение действительно кажется единственным реальным способом достичь того, что вам нужно.

Я собрал некоторый код ниже. Он создает атрибут с именем EndianAttribute это может быть применено на уровне поля в структуре. Я включил определение этого атрибута, и это связанное перечисление, а также изменения в вашем коде, необходимые для его использования.

в качестве примечания, вам не нужно было определять rawData как


для тех из нас, кто без Linq, замена RespectEndianness():

private static void RespectEndianness(Type type, byte[] data) {
    foreach (FieldInfo f in type.GetFields()) {
        if (f.IsDefined(typeof(EndianAttribute), false)) {
            EndianAttribute att = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0];
            int offset = Marshal.OffsetOf(type, f.Name).ToInt32();
            if ((att.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
                (att.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian)) {
                Array.Reverse(data, offset, Marshal.SizeOf(f.FieldType));
            }
        }
    }
}

вот мой вариант-он обрабатывает вложенные структуры и массивы, с предположением, что массивы имеют фиксированный размер, например, помечены [MarshalAs(UnmanagedType.Атрибут ByValArray, SizeConst = Н)].

public static class Serializer
{
    public static byte[] GetBytes<T>(T structure, bool respectEndianness = true) where T : struct
    {
        var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<T>(); in .net 4.5.1
        var bytes = new byte[size];
        var ptr = Marshal.AllocHGlobal(size);

        Marshal.StructureToPtr(structure, ptr, true);
        Marshal.Copy(ptr, bytes, 0, size);
        Marshal.FreeHGlobal(ptr);

        if (respectEndianness) RespectEndianness(typeof(T), bytes);  

        return bytes;
    }

    public static T FromBytes<T>(byte[] bytes, bool respectEndianness = true) where T : struct
    {
        var structure = new T();

        if (respectEndianness) RespectEndianness(typeof(T), bytes);    

        int size = Marshal.SizeOf(structure);  //or Marshal.SizeOf<T>(); in .net 4.5.1
        IntPtr ptr = Marshal.AllocHGlobal(size);

        Marshal.Copy(bytes, 0, ptr, size);

        structure = (T)Marshal.PtrToStructure(ptr, structure.GetType());
        Marshal.FreeHGlobal(ptr);

        return structure;
    }

    private static void RespectEndianness(Type type, byte[] data, int offSet = 0)
    {
        var fields = type.GetFields()
            .Select(f => new
            {
                Field = f,
                Offset = Marshal.OffsetOf(type, f.Name).ToInt32(),
            }).ToList();

        foreach (var field in fields)
        {
            if (field.Field.FieldType.IsArray)
            {
                //handle arrays, assuming fixed length
                var attr = field.Field.GetCustomAttributes(typeof(MarshalAsAttribute), false).FirstOrDefault();
                var marshalAsAttribute = attr as MarshalAsAttribute;
                if (marshalAsAttribute == null || marshalAsAttribute.SizeConst == 0)
                    throw new NotSupportedException(
                        "Array fields must be decorated with a MarshalAsAttribute with SizeConst specified.");

                var arrayLength = marshalAsAttribute.SizeConst;
                var elementType = field.Field.FieldType.GetElementType();
                var elementSize = Marshal.SizeOf(elementType);
                var arrayOffset = field.Offset + offSet;

                for (int i = arrayOffset; i < arrayOffset + elementSize * arrayLength; i += elementSize)                    {
                    RespectEndianness(elementType, data, i);
                }
            }
            else if (!field.Field.FieldType.IsPrimitive) //or !field.Field.FiledType.GetFields().Length == 0
            {
                //handle nested structs
                RespectEndianness(field.Field.FieldType, data, field.Offset);
            }
            else
            {
                //handle primitive types
                Array.Reverse(data, offSet + field.Offset, Marshal.SizeOf(field.Field.FieldType));
            }
        }
    }
}

этот вопрос был потрясающим, и мне очень помогли! Мне нужно было расширить endian changer, хотя, похоже, он не обрабатывает массивы или структуры внутри структур.

    public struct mytest
    {
        public int myint;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
        public int[] ptime;
    }

    public static void SwapIt(Type type, byte[] recvbyte, int offset)
    {
        foreach (System.Reflection.FieldInfo fi in type.GetFields())
        {
            int index = Marshal.OffsetOf(type, fi.Name).ToInt32() + offset;
            if (fi.FieldType == typeof(int))
            {
                Array.Reverse(recvbyte, index, sizeof(int));
            }
            else if (fi.FieldType == typeof(float))
            {
                Array.Reverse(recvbyte, index, sizeof(float));
            }
            else if (fi.FieldType == typeof(double))
            {
                Array.Reverse(recvbyte, index, sizeof(double));
            }
            else
            {
                // Maybe we have an array
                if (fi.FieldType.IsArray)
                {
                    // Check for MarshalAs attribute to get array size
                    object[] ca = fi.GetCustomAttributes(false);
                    if (ca.Count() > 0 && ca[0] is MarshalAsAttribute)
                    {
                        int size = ((MarshalAsAttribute)ca[0]).SizeConst;
                        // Need to use GetElementType to see that int[] is made of ints
                        if (fi.FieldType.GetElementType() == typeof(int))
                        {
                            for (int i = 0; i < size; i++)
                            {
                                Array.Reverse(recvbyte, index + (i * sizeof(int)), sizeof(int));
                            }
                        }
                        else if (fi.FieldType.GetElementType() == typeof(float))
                        {
                            for (int i = 0; i < size; i++)
                            {
                                Array.Reverse(recvbyte, index + (i * sizeof(float)), sizeof(float));
                            }
                        }
                        else if (fi.FieldType.GetElementType() == typeof(double))
                        {
                            for (int i = 0; i < size; i++)
                            {
                                Array.Reverse(recvbyte, index + (i * sizeof(double)), sizeof(double));
                            }
                        }
                        else
                        {
                            // An array of something else?
                            Type t = fi.FieldType.GetElementType();
                            int s = Marshal.SizeOf(t);
                            for (int i = 0; i < size; i++)
                            {
                                SwapIt(t, recvbyte, index + (i * s));
                            }
                        }
                    }
                }
                else
                {
                    SwapIt(fi.FieldType, recvbyte, index);
                }
            }
        }
    }

обратите внимание, что этот код был протестирован только на структурах из int, float, double. Вероятно, испортит, если у вас есть строка там!