loading binary data into a structure
I'm trying to fill a structure (does not have to be an actual struct), with data loaded from a byte[].
There are many different data structures in the byte[], one of them is a string, which is declared as:
UInt16 stringLenght
byte[stringLenght] zeroTerminatedString
I 'c' language this could be handled by declaring a fixed size struct, and instead of a the struct containing the actual string, make a pointer 开发者_StackOverflow社区to the string.
Something like:
UInt16 stringLength
char* zeroTerminatedString
Is there a (smart) way to do something similar in c#? I mean loading binary data from a file/memory and filling it into a structure?
Regards Jakob Justesen
That's not how you'd declare it in C. If the record in the file contains a string then you'd declare the structure similar to:
struct Example {
int mumble; // Anything, not necessarily a string length
char text[42];
// etc...
};
The equivalent C# declaration would look like:
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
private struct Example {
public int mumble;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 42)]
public string text;
// etc...
}
You'd normally use BinaryReader to read the data. But it cannot handle strings like this directly, you have to read them as a byte[] and do the string conversion yourself. You also cannot take advantage of the declarative syntax, you have to write a call for each individual member of the struct.
There's a workaround for that, the Marshal class already knows how to convert unmanaged structures to managed ones with the PtrToStructure() method. Here's a generic implementation, it works for any blittable type. Two versions, a static one that reads from a byte[] and an instance method that was optimized to repeatedly read from a stream. You'd use a FileStream or MemoryStream with that one.
using System;
using System.IO;
using System.Runtime.InteropServices;
class StructTranslator {
public static bool Read<T>(byte[] buffer, int index, ref T retval) {
if (index == buffer.Length) return false;
int size = Marshal.SizeOf(typeof(T));
if (index + size > buffer.Length) throw new IndexOutOfRangeException();
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try {
IntPtr addr = (IntPtr)((long)handle.AddrOfPinnedObject() + index);
retval = (T)Marshal.PtrToStructure(addr, typeof(T));
}
finally {
handle.Free();
}
return true;
}
public bool Read<T>(Stream stream, ref T retval) {
int size = Marshal.SizeOf(typeof(T));
if (buffer == null || size > buffer.Length) buffer = new byte[size];
int len = stream.Read(buffer, 0, size);
if (len == 0) return false;
if (len != size) throw new EndOfStreamException();
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try {
retval = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
}
finally {
handle.Free();
}
return true;
}
private byte[] buffer;
}
Untested, hope it works.
The Marshal should be able to do this for you.
Note that this can only be done to a struct and you will probably need to use the StructLayout attribute.
I'm not 100% sure on how to handle the string or arrays, but BStrWrapper or ArrayWithOffset MIGHT help, also keep on the lookout for similar classes/attributes (I know I have done stuff like this before for binding to native functions).
精彩评论