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.
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)
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.
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:
- 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.
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
. 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.