c# - Marshal.PtrToStructure(并再次返回)和字节顺序交换的通用解决方案

标签 c# .net sockets marshalling endianness

我有一个系统,其中远程代理发送序列化结构(来自嵌入式 C 系统)供我通过 IP/UDP 读取和存储。在某些情况下,我需要发回相同的结构类型。我认为我使用 Marshal.PtrToStructure(接收)和 Marshal.StructureToPtr(发送)进行了很好的设置。但是,一个小问题是网络大端整数需要转换为我的 x86 小端格式才能在本地使用。当我再次将它们送走时,big 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;
}

在编码结构时,我可以通过什么(通用)方式交换字节顺序?我的需要是本例中本地存储的“request.sequence”应该是小端的,以便向用户显示。我不想以特定于结构的方式交换字节序,因为这是一个普遍问题。

我的第一个想法是使用 Reflection,但我对该功能不是很熟悉。另外,我希望会有更好的解决方案,有人可以指出我的方向。提前致谢:)

最佳答案

反射(reflection)似乎是实现您所追求目标的唯一真正途径。

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

关于c# - Marshal.PtrToStructure(并再次返回)和字节顺序交换的通用解决方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2623761/

相关文章:

c# - 使用 Entity Framework 加载嵌套实体/集合

c# - FluentValidation 即服务

c++ - Client Server场景下复杂且相互关联的数据结构

c++ - 如何检查服务器端口是否打开并从 MFC 连接

c# - 在 ADO.NET 中合并对两个数据库的查询

c# - 以编程方式绑定(bind) DataGridTemplateColumn

c# - 成功的 Visual Studio C# 构建不会创建程序集

.net - 持久消息/服务总线 - 自己动手还是冒险学习曲线?

java - Socket.io 无需加入即可收听房间

c# - 如何使用 HTTP GET 接收传递给 ASP.NET 页面的值?