tags:

views:

176

answers:

7

Hello again, time for another off the wall question. I am writing an MD2 loader for my small 3D engine project. In my old language (C) I could define a structure and then read() from an open file directly into the structure. I have a structure to hold the header information from the MD2 file, as follows:

[StructLayout(LayoutKind.Sequential)]
public struct MD2_Header
{
    public int FourCC;
    public int Version;
    public int TextureWidth;
    public int TextureHeight;
    public int FrameSizeInBytes;
    public int NbrTextures;
    public int NbrVertices;
    public int NbrTextureCoords;
    public int NbrTriangles;
    public int NbrOpenGLCmds;
    public int NbrFrames;
    public int TextureOffset;
    public int TexCoordOffset;
    public int TriangleOffset;
    public int FrameOffset;
    public int OpenGLCmdOffset;
    public int EndOffset;
}

In my reader code, I would like to do something like:

// Suck the MD2 header into a structure, it is 68 bytes long.
Classic.Util.MD2_Header md2hdr = new Classic.Util.MD2_Header();
md2hdr = reader.ReadBytes(sizeof(Classic.Util.MD2_Header));

I realize this is not correct, as it breaks type safety somewhat oddly, but you get the idea of what I want to accomplish. I could do this with separate calls to reader.ReadInt32(), but I am curious if there is anyway to get this to work the way I am wanting using normal library calls.

I have looked a little into the Marshal.Copy() method, but it seems to be for going between managed and unmanaged memory, which is not really what I am doing here.

Any suggestions?

A: 

It can get a bit complex in C#, but you could set things up where you can memcopy the bytes from a byte array to the struct.

Toby
+1  A: 

What you can do is read the bytes into a buffer of the approprate size, use fixed (int* = &md2hdr.FourCC) to obtain a pointer to the start of your structure, cast the pointer to your structure to byte*, and copy the bytes manually.

jdv
+5  A: 

Read the byte stream to byte array, name it packet, and try following:

GCHandle pinned = GCHandle.Alloc(packet, GCHandleType.Pinned);
MD2_Header h = (MD2_Header)Marshal.PtrToStructure(pinned.AddrOfPinnedObject(), typeof(MD2_Header));
pinned.Free();
Yossarian
This circumvents the GC in a pretty substantial way, something I want to avoid.
Chris D.
+3  A: 

You can use Marshal.PtrToStructure to copy from a pointer directly into your structure in one shot. By

byte[] data = reader.ReadBytes(...);
fixed (byte* bytes = data)
{
     Classic.Util.MD2_Header md2hdr = 
          (Classic.Util.MD2_Header)Marshal.PtrToStructure(
               Marshal.UnsafeAddrOfPinnedArrayElement(data, 0),
               typeof(Classic.Util.MD2_Header)
          );
}
Reed Copsey
This is a little cleaner than Yossarian's solution, but still involves a lot of muddling with the GC I would rather avoid. Also, a "fixed" section requires compiling with the /unsafe parameter.
Chris D.
@Chris D.: Pretty much any time you're messing with bytes and trying to copy memory directly, you ~should~ be in /unsafe. That being said, you can do this by lots of copying otherwise, but this is probably the most direct answer to your question, IMO...
Reed Copsey
+3  A: 

A structure in C and a structure in C# are two completely different things. A structure in C is used both for value types and reference types, while a structure in C# is only used for value types.

A value type should represent a single value, but what you have is plenty of values, so you should use a class instead. The recommended maximum size for a structure in .NET is 16 bytes, and you have more than four times as much data.

A class with properties and a constructor that takes a byte array would look like this:

public class MD2_Header {

  public int FourCC { get; set; }
  public int Version { get; set; };
  public int TextureWidth { get; set; };
  public int TextureHeight { get; set; };
  public int FrameSizeInBytes { get; set; };
  public int NbrTextures { get; set; };
  public int NbrVertices { get; set; };
  public int NbrTextureCoords { get; set; };
  public int NbrTriangles { get; set; };
  public int NbrOpenGLCmds { get; set; };
  public int NbrFrames { get; set; };
  public int TextureOffset { get; set; };
  public int TexCoordOffset { get; set; };
  public int TriangleOffset { get; set; };
  public int FrameOffset { get; set; };
  public int OpenGLCmdOffset { get; set; };
  public int EndOffset { get; set; };

  public MD2_Header(byte[] values) {
    FourCC = BitConverter.ToInt32(values, 0);
    Version = BitConverter.ToInt32(values, 4);
    TextureWidth = BitConverter.ToInt32(values, 8);
    TextureHeight = BitConverter.ToInt32(values, 12);
    FrameSizeInBytes = BitConverter.ToInt32(values, 16);
    NbrTextures = BitConverter.ToInt32(values, 20);
    NbrVertices = BitConverter.ToInt32(values, 24);
    NbrTextureCoords = BitConverter.ToInt32(values, 28);
    NbrTriangels = BitConverter.ToInt32(values, 32);
    NbrOpenGLCmds = BitConverter.ToInt32(values, 36);
    NbrFrames = BitConverter.ToInt32(values, 40);
    TextureOffset = BitConverter.ToInt32(values, 44);
    TexCoordOffset = BitConverter.ToInt32(values, 48);
    TriangleOffset = BitConverter.ToInt32(values, 52);
    FrameOffset = BitConverter.ToInt32(values, 56);
    OpenGLCmdOffset = BitConverter.ToInt32(values, 60);
    EndOffset = BitConverter.ToInt32(values, 64);
  }

}
Guffa
I went ahead and used this method, I agree completely with your distinction between value and reference types. Your solution is better design, certainly, I was just curious if there was a simple method to emulate my old style of doing things. :-)
Chris D.
-1... totally ignoring the proper answers which point to that being EXPLICITELY supported for interop reasons.
TomTom
@Chris: It's possible, but not as simple. You would need to use attributes that specify exactly how the members are organised in the structure to make sure that there is no padding and that they are in the right order. Then you need to use the garbage collector to pin the data in memory, and marshalling or unsafe code to copy the data into the structure.
Guffa
@TomTom: Are you talking about the answers that are ignoring that it should not be a structure in the first place?
Guffa
+1  A: 

You could use marshaling to handle the copying. No need to write the code to handle it.

//create object
Classic.Util.MD2_Header md2hdr = new Classic.Util.MD2_Header();
Classic.Util.MD2_Header another = new Classic.Util.MD2_Header();
byte[] mem = new byte[Marshal.SizeOf(typeof(MD2_Header))];

//allocate unmanaged memory
IntPtr hmem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Classic.Util.MD2_Header)));

//copy structure to unmanaged memory
Marshal.StructureToPtr(md2hdr, hmem, false);

//copy to managed memory
Marshal.Copy(hmem, mem, 0, mem.Length);

//copy unmanaged memory to structure
another = (Classic.Util.MD2_Header)Marshal.PtrToStructure(hmem, typeof(Classic.Util.MD2_Header));

//free unmanaged memory
Marshal.FreeHGlobal(hmem);
Jeff M
Color formatting is automatic if the JS lib SO is using recognizes your code. It looks like you're setting some text to bold here, which might be throwing it off...
Tim Coker
Acutally, I looked at the src of your comment and you're doing it wrong. For source, on SO, all you have to do is have to line breaks preceeding the code and indent the lines of your code by four spaces. The rest is automatic. You can't make certain lines bold, though.
Tim Coker
Originally I left out the bold but put it there later when I couldn't get the coloring right to highlight key parts. I'll try to fix it to get the coloring now. Thanks. :)
Jeff M
A: 

I know you already have the answer and it is a good answer.

I thought you might get some value from a blog post I did on some of the option available in .NET to achieve this.

Structure from binary data

And a corresponding post for the reverse

Binary data from a structure

Chris Taylor
Thanks for these links, they are good reads.
Chris D.