C#中与C的fread文件I/O相当的函数

7

有人能告诉我如何在C# .NET 2版本中以直接方式将字节数组转换为结构体吗?就像在C语言中熟悉的fread一样,到目前为止我读取字节流并自动填充结构体并没有取得太大成功。我看到一些实现中使用了指针操作,通过使用unsafe关键字来进行托管代码。

请查看这个示例:

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();
      }
   }
}

我在foobarStruct中有两个构造函数的原因是:
  • 不能有一个空的构造函数。
  • 在实例化结构时,将一块内存(作为字节数组)传递给构造函数。
这种实现是否足够好,还是有更简洁的方法可以实现? 编辑:我不想使用ISerializable接口或其实现。我正在尝试读取二进制图像以确定使用的字段,并使用PE结构确定其数据。

3
即使在 C 语言中,由于填充和对齐的考虑,直接使用 fread 函数读取到一个结构体中是一个非常糟糕的想法。 - Gregory Pakosz
你考虑过在这里使用序列化吗? - Captain Comic
这个操作必须在不安全的块内执行,因为它是不安全的。一个结构体可能包含指向引用类型等成员。你要求将未知的磁盘字节扔进可能包含任何指针的结构中。让框架验证你试图做什么是太过分了,因此需要使用不安全块。你仍然可以这样做,但框架必须采取“自行解决”的方法。序列化可以处理底层问题,但并不适用于所有情况。我认为你不会比所示代码做得更好。 - Michael A. McCloskey
感谢大家的参与!祝大家节日快乐/圣诞快乐 :) - t0mm13b
2个回答

10

使用P/Invoke marshaller并没有什么问题,它是安全的,不需要使用unsafe关键字。如果使用不当,只会产生错误的数据。相比显式编写反序列化代码, 使用它要容易得多,特别是在文件包含字符串时。不能使用BinaryReader.ReadString(),因为它假定该字符串是由BinaryWriter编写的。但是请确保使用struct声明数据的结构,否则this.GetType()将无法正常工作。

这里是一个通用类,可使其对任何结构声明起作用:

  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;
    }

文件中数据结构的示例声明:

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

您需要调整结构声明和属性以与文件中的数据匹配。读取文件的示例代码:

  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));
    }
  }

3
你可能需要使用BinaryReader,它允许你以二进制形式读取原始类型。
byte[]创建一个MemoryStream,然后使用其中的BinaryReader。你应该能够读取结构体并相应地填充对象。

我知道这是一篇旧帖子,但对于那些在2019年或之后寻找答案的人来说,这正是我处理此事的方法。我创建了扩展方法,通过BinaryReader处理验证并使用原始byte[]数据填充我的对象。如果您正在使用.NET Core 2.1或更高版本,则还可以使用Span<T>而不是字节数组。 - Steven Hoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接