Marshal.PtrToStructure (и обратно) и универсальное решение для обмена порядком байтов - PullRequest
17 голосов
/ 12 апреля 2010

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

Вот функции, о которых идет речь:

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

Каким (общим) способом я могу поменять порядковый номер при сортировке структур? Моя потребность в том, чтобы локально сохраненный «request.sequence» в этом примере должен был иметь порядок байтов для отображения пользователю. Я не хочу менять порядковый номер в структуре, поскольку это общая проблема.

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

Ответы [ 4 ]

20 голосов
/ 12 апреля 2010

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

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

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

Обратите внимание, что для этого требуется использование C # 3.0 / .NET 3.5, поскольку я использую LINQ и анонимные типы в функции, выполняющей работу. Впрочем, без этих функций было бы несложно переписать функцию.

[AttributeUsage(AttributeTargets.Field)]
public class EndianAttribute : Attribute
{
    public Endianness Endianness { get; private set; }

    public EndianAttribute(Endianness endianness)
    {
        this.Endianness = endianness;
    }
}

public enum Endianness
{
    BigEndian,
    LittleEndian
}

private static void RespectEndianness(Type type, byte[] data)
{
    var fields = type.GetFields().Where(f => f.IsDefined(typeof(EndianAttribute), false))
        .Select(f => new
        {
            Field = f,
            Attribute = (EndianAttribute)f.GetCustomAttributes(typeof(EndianAttribute), false)[0],
            Offset = Marshal.OffsetOf(type, f.Name).ToInt32()
        }).ToList();

    foreach (var field in fields)
    {
        if ((field.Attribute.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
            (field.Attribute.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian))
        {
            Array.Reverse(data, field.Offset, Marshal.SizeOf(field.Field.FieldType));
        }
    }
}

private static T BytesToStruct<T>(byte[] rawData) where T : struct
{
    T result = default(T);

    RespectEndianness(typeof(T), rawData);     

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

    RespectEndianness(typeof(T), rawData);     

    return rawData;
}
2 голосов
/ 10 декабря 2010

Для тех из нас, у кого нет 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));
            }
        }
    }
}
1 голос
/ 16 января 2014

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

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));
            }
        }
    }
}
0 голосов
/ 10 июня 2011

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

    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. Вероятно, испортится, если у вас там есть строка!

...