A C# equivalent of C's fread file i/o
Asked Answered
A

2

7

Can anyone tell me how to get a array of bytes into a structure in a direct fashion in C# .NET version 2? Like the familiar fread as found in C, so far I have not had much success in reading a stream of bytes and automatically filling a structure. I have seen some implementations where there is pointer hocus-pocus in the managed code by using the unsafe keyword.

Have a look at this sample:

public unsafe struct foobarStruct{

   /* fields here... */

   public foobarStruct(int nFakeArgs){
      /* Initialize the fields... */
   }

   public foobarStruct(byte[] data) : this(0) {
      unsafe {
         GCHandle hByteData = GCHandle.Alloc(data, GCHandleType.Pinned);
         IntPtr pByteData = hByteData.AddrOfPinnedObject();
         this = (foobarStruct)Marshal.PtrToStructure(pByteData, this.GetType());
         hByteData.Free();
      }
   }
}

The reason I have two constructors in foobarStruct

  • Is there cannot be an empty constructor.
  • Pass in a block of memory (as a byte array) into the constructor when instantiating the structure.

Is that implementation good enough or is there a much cleaner way to achieve this?

Edit: I do not want to use the ISerializable interface or its implementation. I am trying to read a binary image to work out the fields used and determine its data using the PE structures.

Andaman answered 20/12, 2009 at 13:38 Comment(4)
even in C, it's a very bad idea to directly fread into a struct due to padding and alignment considerationsMalo
Have you considered using Serialization here instead?Cloistered
This operation must occur inside an unsafe block because it is, well, unsafe. A structure could contain members that point to reference types, etc. You are asking to take unknown bytes of disk and toss them into a structure that might contain pointers to anything. It's too much to ask the framework to validate what you're trying to do, hence the unsafe block. You can still do it, but the framework must take a "you're on your own" approach. Serialization handles the underlying concerns for you, but it doesn't fit all scenarios. I don't think you will do much better than the code shown.Zoi
Thanks guys for your input! Have a happy Season Greetings/Christmas :)Andaman
H
10

There isn't anything wrong with using the P/Invoke marshaller, it is not unsafe and you don't have to use the unsafe keyword. Getting it wrong will just produce bad data. It can be a lot easier to use than explicitly writing the deserialization code, especially when the file contains strings. You can't use BinaryReader.ReadString(), it assumes that the string was written by BinaryWriter. Make sure however that you declare the structure of the data with a struct declaration, this.GetType() is not likely to work out well.

Here's a generic class that will make it work for any structure declaration:

  class StructureReader<T> where T : struct {
    private byte[] mBuffer;
    public StructureReader() {
      mBuffer = new byte[Marshal.SizeOf(typeof(T))];
    }
    public T Read(System.IO.FileStream fs) {
      int bytes = fs.Read(mBuffer, 0, mBuffer.Length);
      if (bytes == 0) throw new InvalidOperationException("End-of-file reached");
      if (bytes != mBuffer.Length) throw new ArgumentException("File contains bad data");
      T retval;
      GCHandle hdl = GCHandle.Alloc(mBuffer, GCHandleType.Pinned);
      try {
        retval = (T)Marshal.PtrToStructure(hdl.AddrOfPinnedObject(), typeof(T));
      }
      finally {
        hdl.Free();
      }
      return retval;
    }

A sample declaration for the structure of the data in the file:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct Sample {
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 42)]
  public string someString;
}

You'll need to tweak the structure declaration and the attributes to get a match with the data in the file. Sample code that reads a file:

  var data = new List<Sample>();
  var reader = new StructureReader<Sample>();
  using (var stream = new FileStream(@"c:\temp\test.bin", FileMode.Open, FileAccess.Read)) {
    while(stream.Position < stream.Length) {
      data.Add(reader.Read(stream));
    }
  }
Housen answered 20/12, 2009 at 16:4 Comment(0)
Q
3

You probably want to use a BinaryReader which allows you to read in primitive types in binary form.

Create a MemoryStream from the byte[] and then use the BinaryReader from that. You should be able to read out the structure and fill in your object accordingly.

Quadric answered 20/12, 2009 at 14:9 Comment(1)
I realize that this is an old post, however for those that find are looking for an answer in 2019 or later this is exactly how I went about doing this. I created extension methods on BinaryReader that handled validation and filling my objects with raw byte[] data. If you are using .net core 2.1 or later you can also use Span<T> instead of byte array.Ochre

© 2022 - 2024 — McMap. All rights reserved.