C#pack 1 StructLayout网络

时间:2013-11-14 20:09:23

标签: c# networking marshalling structlayout

我正在尝试从服务器向客户端发送缓冲区,这是我自己制作的。它适用于TCP上的套接字。

我有一个需要发送的结构:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct loginStruct
{

    public string userName;
    public string password;

    public loginStruct(string userName, string password)
    {
        this.userName = userName;
        this.password = password;
    }
}

我将这些函数从字节数组转换为struct,从struct转换为字节数组:

    public static byte[] StructToByteArray(object obj)
    {
        int len = Marshal.SizeOf(obj);
        byte[] arr = new byte[len];

        IntPtr ptr = Marshal.AllocHGlobal(len);
        Marshal.StructureToPtr(obj, ptr, false);
        Marshal.Copy(ptr, arr, 0, len);

        Marshal.FreeHGlobal(ptr);
        return arr;

    }
    public static void ByteArrayToStruct(byte[] buffer, ref object obj)
    {
        int len = Marshal.SizeOf(obj);

        IntPtr i = Marshal.AllocHGlobal(len);
        Marshal.Copy(buffer, 0, i, len);
        obj = Marshal.PtrToStructure(i, obj.GetType());

        Marshal.FreeHGlobal(i);
    }

在客户端我收到缓冲区,但是当客户端尝试使用ByteArrayToStruct函数时,我遇到了运行时错误。

1 个答案:

答案 0 :(得分:0)

好的,在尝试轻松解析来自专有服务器的响应时,我有同样的想法。这是根据您的具体情况调整的简化示例。

首先,您需要一些扩展才能使这一切变得更加容易。请注意,要执行此操作,您需要使用.NET 3.5或更高版本或查看答案here

现在,这就是我为扩展课程所做的工作:

public static class EndianExtensions {
    /// <summary>
    /// Convert the bytes to a structure in host-endian format (little-endian on PCs).
    /// To use with big-endian data, reverse all of the data bytes and create a struct that is in the reverse order of the data.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="buffer">The buffer.</param>
    /// <returns></returns>
    public static T ToStructureHostEndian<T>(this byte[] buffer) where T : struct {
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        T stuff = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        return stuff;
    }

    /// <summary>
    /// Converts the struct to a byte array in the endianness of this machine.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="structure">The structure.</param>
    /// <returns></returns>
    public static byte[] ToBytesHostEndian<T>(this T structure) where T : struct {
        int size = Marshal.SizeOf(structure);
        var buffer = new byte[size];
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), true);
        handle.Free();
        return buffer;
    }

    public static Dictionary<string, string> GetTypeNames<T>(this T structure) where T : struct {
        var properties = typeof(T).GetFields();

        var dict = new Dictionary<string, string>();

        foreach (var fieldInfo in properties) {
            string[] words = fieldInfo.Name.Split('_');
            string friendlyName = words.Aggregate(string.Empty, (current, word) => current + string.Format("{0} ", word));
            friendlyName = friendlyName.TrimEnd(' ');
            dict.Add(fieldInfo.Name, friendlyName);
        }
        return dict;
    }
}

(请注意,上面的一些内容是从CodeProject上的源代码中采样的,所有内容都在CPOL license下)

另一个需要注意的重要事项是GetTypeNames扩展程序可用于为您的属性获取友好名称,如果您使用CamelCaps和下划线,您需要空格。

使这项工作的最后一个关键部分(至少,对于我的特定情况)是在 reverse 中声明你的结构。这是因为我的服务器使用了大字节序。您可能希望尝试使用和不更改字节顺序 - 无论哪种方式适合您。

所以要真正使用它,这就是你做的事情:

  1. 声明你的结构。由于我需要在发送之前将其放入大端,所以我的所有都是相反的:
  2. 
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct Foo {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string User_Name;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string Password;
    };
    
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct Bar {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string Password;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string User_Name;
    };
    

    现在,上面假设发送和接收数据缓冲区的实际内容定义不同,因此在您的情况下,您只需要定义其中一个结构。请注意,它们以相反的顺序指定;再次,这是因为我需要以big-endian格式传输它。

    现在要做的就是创建要发送的结构:

    // buffer for storing our received bytes
    var barBuf = new byte[64];
    
    // struct that we're sending
    var fuz = new Foo {
        User_Name = "username",
        Password = "password"
    };
    
    // get the byte equivalent of fuz
    var fuzBytes = fuz.ToBytesHostEndian().Reverse().ToArray();
    
    // simulates sock.send() and sock.receive()
    // note that this does NOT simulate receiving big-endian data!!
    fuzBytes.CopyTo(barBuf, 0);
    
    // do the conversion from bytes to struct
    barBuf = barBuf.Reverse().ToArray();
    
    // change this to ToStructureHostEndian<Bar>() if receiving big endian
    var baz = barBuf.ToStructureHostEndian<Foo>();
    // get the property names, friendly and non-friendly
    var bazDict = baz.GetTypeNames();
    
    // change this to typeof(Bar) if receiving big endian
    var bazProps = typeof(Foo).GetFields();
    
    
    // loop through the properties array
    foreach (var fieldInfo in bazProps) {
        var propName = fieldInfo.Name;
        // get the friendly name and value
        var fieldName = bazDict[propName];
        var value = fieldInfo.GetValue(baz);
    
        // do what you want with the values
        Console.WriteLine("{0,-15}:{1,10}", fieldName, value);
    }
    

    重要的是要注意,通过使用sock.Send()模拟sock.Receive()CopyTo()命令,它不会导致barBuf中的大端数组。我相应地修改了代码,但是如果您使用它来接收大端数据,只需更改代码中指示的行。

    我希望这会有所帮助。我花了很多时间来弄清楚自己,因为这些信息在多个来源中分散开来。