Marshal.PtrToStructure(然后再次返回)和字节顺序交换的通用解决方案

发布于 2024-08-28 13:05:06 字数 1770 浏览 11 评论 0 原文

我有一个系统,其中远程代理发送序列化结构(来自嵌入式 C 系统)供我通过 IP/UDP 读取和存储。在某些情况下,我需要发回相同的结构类型。我认为我使用 Marshal.PtrToStructure (接收)和 Marshal.StructureToPtr (发送)进行了很好的设置。然而,一个小问题是网络大端整数需要转换为我的 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,但我对该功能不是很熟悉。另外,我希望有人能指出更好的解决方案。提前致谢 :)

I have a system where a remote agent sends serialized structures (from an embedded C system) for me to read and store via IP/UDP. In some cases I need to send back the same structure types. I thought I had a nice setup using Marshal.PtrToStructure (receive) and Marshal.StructureToPtr (send). However, a small gotcha is that the network big endian integers need to be converted to my x86 little endian format to be used locally. When I'm sending them off again, big endian is the way to go.

Here are the functions in question:

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

And a quick example structure that might be used like this:

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

Where the structure in question looks like:

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

What (generic) way can I swap the endianness when marshalling the structures? My need is such that the locally stored 'request.sequence' in this example should be little-endian for displaying to the user. I don't want to have to swap the endianness in a structure-specific way since it's a generic problem.

My first thought was to use Reflection, but I'm not very familiar with that feature. Also, I hoped that there would be a better solution out there that somebody could point me towards. Thanks in advance :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

往事随风而去 2024-09-04 13:05:06

反思似乎是实现你所追求的目标的唯一真正方法。

我在下面整理了一些代码。它创建一个名为 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;
}

Reflection does seem like the only real way to accomplish what you're after.

I've put together some code below. It creates an attribute called EndianAttribute that can be applied at the field level on a struct. I've included the definition for this attribute and it's associated enum, as well as the modifications to your code necessary to use it.

As a side note, you did not need to define rawData as a ref parameter.

Note that this does require the use of C# 3.0/.NET 3.5, since I'm using LINQ and anonymous types in the function doing the work. It would not be difficult to rewrite the function without these features, though.

[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;
}
太阳哥哥 2024-09-04 13:05:06

对于我们这些没有 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));
            }
        }
    }
}

For those of us without Linq, a replacement 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));
            }
        }
    }
}
ζ澈沫 2024-09-04 13:05:06

这是我的变体 - 它处理嵌套结构和数组,假设数组具有固定大小,例如用 [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));
            }
        }
    }
}

Here's my variation - it handles nested structs and arrays, with the assumption that arrays are of a fixed size, eg marked with a [MarshalAs(UnmanagedType.ByValArray, SizeConst = N)] attribute.

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));
            }
        }
    }
}
沦落红尘 2024-09-04 13:05:06

这个问题太棒了,对我帮助很大!我需要扩展字节序更改器,因为它似乎无法处理结构中的数组或结构。

    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 组成的结构上进行了测试。如果里面有一根绳子,可能会搞砸!

This question was awesome and helped me a lot! I needed to expand on the endian changer though as it doesn't seem to handle arrays or structs within structs.

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

Note this code was only tested on structs made of int, float, double. Will probably mess up if you have a string in there!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文