6

I'm reading binary data (either by network stream or from files) that is predefined several years ago. I currently read this data by reading it into a byte array and then convert the array into the fields I need with System.BitConverter. Needless to say this is timeconsuming and error prone.

I wish I could use ISerializable, but I don't see how this can be implemented for a predefined structure.

I hope for pointers on how to improve my current strategy...

4 Answers 4

5

Rozon, I had this exact same problem, and I was about to give up on the solution until I found this question here, and read Joe's response to it. Joe mentions the Marshal object to move data between regular MEMORY and a managed object... Here is what I did, and what worked like magic for me... If you haven't already figured this out, here is a whole break-down of what I did, and how I got it to work.. (Thank you Joe!)...

First, you must be able to create a managed instance of your structure. This shouldn't be too hard, it just means a lot of extra attributes on your structure.

For example, here's a data structure that holds some information about a server. Of course, the string sizes are not accurate, so make SURE your string sizes ARE!

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack=1)]
struct TServerInformation {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
    public string ComputerName;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
    public string HostName;

    [MarshalAs(UnmanagedType.U4)]
    public Int32 IPAddress;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
    public string AdminName;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
    public string WindowsDir;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
    public string Domain;
};

Also, make CERTAIN you are using the correct Charset, in the case that it was compiled to use Unicode, you need to make sure the Charset reflects that.

Now, here's the cool part...

To get the SIZE of the struct, (and the size of the buffer you need to hold it), you need only to use:

byte[] buff = new byte[Marshal.SizeOf(pStruct)];

And, with that, you can now do whatever it is you do to GET this data delivered to you, (which I assume you already have all that figured out...) and then the magic can begin. What we do, is allocate a block of unmanaged memory, then copy the buffer into the block of memory, then copy it back out into our structure, and free the unmanaged memory block. This can easily be put into an extension function that can operate on any byte[], giving you the ability to call something like:

pStruct = (TServerInformation)buff.FromMemory();

The following code is what worked wonders for me. Oh, and do not forget to put try/catch/finally blocks around this in production code...

// Create our Managed Object...
TServerInformation pStruct = new TServerInformation();
// Allocate our block of unmanaged memory...
IntPtr pMem = Marshal.AllocHGlobal(Marshal.SizeOf(pStruct));
// Copy our buff buffer into that new block of memory...
Marshal.Copy(buff, 0, pMem, buff.Length);
// Type-cast that block of memory with the results of the Marshal object's help...
pStruct = (TServerInformation)Marshal.PtrToStructure(pMem, pStruct.GetType());
// Free that block of memory, that is no longer needed now that the data exists in managed form.
Marshal.FreeHGlobal(pMem);

And there you have it! This avoids all the extra header info that is included when you use Serialization, AND, it's about the only way I've found to get accurate "sizeof()" data from a managed structure. I'm sure there may be other ways to do this, but so far, this is the best I have found, after about 5 days of searching, hacking, searching, debugging, hacking, and searching some more... :)

If this works for you, let me know... I'm curious to see if it fixes your problem as well.

Sign up to request clarification or add additional context in comments.

2 Comments

It works for me. Saved me alot of time implementing, and it's easy to keep maintained. +1 for giving much more code than Joe.
@rozon - That's great! I'm glad that helped you out. Don't forget, Skeet brings up some good points about Endianess, but if you aren't dealing with data that could possibly come from a Big Endian source, you don't have to worry about this. As long as the data going in, is exactly what you need coming out, this will work for you. Just be careful with the various string types, etc... They can really screw you up if you Marshal them incorrectly...
4

One way is to create a MemoryStream around the byte array, then use a BinaryReader. That allows you to parse primitive values etc very easily.

However, it does depend on whether the endianness of the data is appropriate. I have an EndianBinaryReader class in MiscUtil which can help you if the built-in one isn't appropriate.

7 Comments

+1: I like this but IMHO it makes for alot of parsing compared to the solution suggested by @Joe.
@rozon: Yes, but it keeps everything explicit rather than relying on an in-memory representation. I'm personally keen on making serialization formats very explicit and therefore easily documented.
@Jon: The formats are very explicit and defined by a PLC system way back. The .NET in-memory representation is guarantied by MarshalAs and FieldOffset. When they are set correct I can keep serialization and deserialization in sync with less code (I think?).
@rozon: And how are you going to specify the endianness when marshalling straight into memory, just as one example of how it can go wrong?
@Jon: I see your point. How will these differ in speed? I imagine that the solution from @Joe is faster, but I'm still learning.
|
2

The Marshal interop classes and methods (namely, PtrToStruct and StructToPtr) can help here as well. See: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx

Comments

1

hope this help:

  //set your byte data instead of null
        byte[] data = null;

        MemoryStream stream = new MemoryStream();
        stream.Write(data,0,data.Length);

        BinaryFormatter formatter = new BinaryFormatter();

        Type s1 = (Type)formatter.Deserialize(stream);

1 Comment

And how can this fit a predefined data structure?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.