2013-11-14 19 views
0

我想从服务器发送一个缓冲区到我自己创建的客户端。它适用于TCP上的套接字。C#pack 1 StructLayout联网

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

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

而且我得到了这些功能,从字节数组转换为结构,并从结构到字节数组:

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函数时,我得到了运行时错误。

+0

你如何发送数据?你能张贴用于通过套接字发送/接收的代码吗?似乎你发布的内容应该有效,错误可能是由错误的传输引起的。 –

+0

什么是运行时异常? –

+0

我不会在sizeof调用或PtrToStructure调用中使用“obj”,而是引用结构本身。 obj引用可能为null,因为它的类型是“object”而不是“loginStruct”。另外,有没有一个原因,你为什么不使用任何序列化器,并“做到这一点”?如果二进制格式是一个给定的,我仍然建议使用BinaryWriter/BinaryReader与编组(编码/安全)speedbump的内存流。 –

回答

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采样)

另一个需要注意的重要事项是,如果使用CamelCaps并且在需要空格的地方突出显示,则可以使用GetTypeNames扩展名为您的属性获取友好名称。

以使这项工作(至少对于我的具体情况),最后一个关键部分是在反向来声明结构。这是因为我的服务器使用了大的字节顺序。您可能想要在不改变排序顺序的情况下尝试,无论哪种方式适用于您。

所以实际使用此,这里就是你要做的:

  1. 声明你的结构。因为我需要付诸大端发射之前,所有的煤矿都在反向:

[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中有一个大的排序数组。我相应地修改了代码,但是如果您使用它来接收大端数据,只需更改代码中指示的行。

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