简体   繁体   English

C#pack 1 StructLayout网络

[英]C# pack 1 StructLayout networking

I am trying to send a buffer from a server to client which I have made by myself. 我正在尝试将缓冲区由服务器发送到我自己创建的客户端。 It works with sockets on TCP. 它与TCP上的套接字一起使用。

I have a struct that I need to send: 我有一个需要发送的结构:

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

And I got these functions to convert from byte array to struct and from struct to byte array: 我得到了以下功能,可以将字节数组转换为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);
    }

In the client I receive the buffer but when the client is trying to use the ByteArrayToStruct function I got a run time error. 在客户端中,我收到缓冲区,但是当客户端尝试使用ByteArrayToStruct函数时,出现运行时错误。

Okay I had the same exact thing in mind while trying to easily parse the responses from a proprietary server. 好的,在尝试轻松解析专有服务器的响应时,我有同样的想法。 Here's a simplified example adjusted to your particular case. 这是一个适合您特定情况的简化示例。

First off you need a few extensions to make this a whole lot easier. 首先,您需要一些扩展才能使此操作变得更加容易。 Note that to do this you need to use .NET 3.5 or higher OR see the answer here . 请注意,要执行此操作,您需要使用.NET 3.5或更高版本,或者在此处查看答案。

Now, here's what I've got working for my extensions class: 现在,这是我为扩展类工作的内容:

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

(Note that some the above was sampled from sources on CodeProject, all of which are under the CPOL license ) (请注意,上面的一些内容是从CodeProject上的样本中获得的,所有这些都在CPOL许可下

Another important thing to note is that the GetTypeNames extension can be used to get a friendly name for your properties if you use CamelCaps and underscores where you'd want spaces. 另一个需要注意的重要事项是,如果在需要空格的地方使用CamelCaps和下划线,则可以使用GetTypeNames扩展名来获取属性的友好名称。

The final key portion to making this work (at least, for my particular case) is to declare your structs in reverse . 进行此工作的最后一个关键部分(至少对于我的特定情况而言)是对您的结构进行反向声明。 This is because my server used big endianness. 这是因为我的服务器使用大字节序。 You may want to try it with and without changing endianness -- whichever works for you. 您可能想尝试使用字节序,而无需更改字节序-无论哪种都适合您。

So to actually use this, here's what you do: 因此,要实际使用此功能,请执行以下操作:

  1. Declare your struct. 声明您的结构。 Since I needed to put it into big endian before transmitting, all of mine are in reverse: 由于我需要在传输之前将其放入大字节序,因此我的全部都是相反的:

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

Now the above assumes that the actual contents of the send and receive data buffers are defined differently so in your case you would simply need to define one of those structs. 现在,上面假设发送和接收数据缓冲区的实际内容定义不同,因此,在您的情况下,您只需要定义这些结构之一即可。 Note that they are specified in reverse order; 请注意,它们以相反的顺序指定; again, this is because of the fact that I needed to transmit it in big-endian format. 同样,这是因为我需要以big-endian格式传输它。

Now all that has to be done is create the struct to send: 现在所有要做的就是创建要发送的结构:

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

It's important to note that by simulating the sock.Send() and sock.Receive() commands by using CopyTo() , it does not result in a big endian array in barBuf . 重要的是要注意,通过使用CopyTo()模拟sock.Send()sock.Receive()命令,它不会在barBuf导致大端数组。 I have modified the code accordingly, but in case you do use this to receive big endian data, just change the lines indicated in the code. 我已经相应地修改了代码,但是如果您确实使用它来接收大端数据,则只需更改代码中指示的行即可。

I hope this helps. 我希望这有帮助。 It took me a great deal of time to figure out myself as this information was spread apart in multiple sources. 由于此信息分散在多个来源中,因此花了我很多时间来弄清楚自己。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM