【问题标题】:C#, read structures from binary fileC#,从二进制文件中读取结构
【发布时间】:2011-05-08 17:37:26
【问题描述】:

我想从二进制文件中读取结构。 在 C++ 中,我会这样做:

stream.read((char*)&someStruct, sizeof(someStruct));

C#中有类似的方法吗? BinaryReader 仅适用于内置类型。在 .NET 4 中有一个 MemoryMappedViewAccessor。它提供了像Read<T> 这样的方法,这似乎是我想要的,除了我必须手动跟踪我想要读取的文件中的位置。 有没有更好的办法?

【问题讨论】:

标签: c# file binary structure


【解决方案1】:
public static class StreamExtensions
{
    public static T ReadStruct<T>(this Stream stream) where T : struct
    {
        var sz = Marshal.SizeOf(typeof(T));
        var buffer = new byte[sz];
        stream.Read(buffer, 0, sz);
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        var structure = (T) Marshal.PtrToStructure(
            pinnedBuffer.AddrOfPinnedObject(), typeof(T));
        pinnedBuffer.Free();
        return structure;
    }
}

您需要确保使用 [StructLayout] 和可能的 [FieldOffset] 注释声明您的结构以匹配文件中的二进制布局

编辑:

用法:

SomeStruct s = stream.ReadStruct<SomeStruct>();

【讨论】:

  • @jesperll:这是一个非常糟糕的主意,尤其是在结构不平坦的情况下。如果结构中的任何位置都有指针,则引用的结构/类将不会写入输出。更糟糕的是,当回读时,它会指向一个无效的内存空间。
  • 是的。如果你的结构中有数组之类的东西,你就会遇到问题,因为它们不是值类型
  • 但是如果你用它来解析一个文件格式,其中大部分是简单类型的标题块,那么它是非常可行的
  • +1 这是完全可行的,我使用几乎相同的代码从 ASF 文件中读取二进制结构 - @casperOne 我不认为问题是要求复杂的对象序列化/反序列化机制
  • 非常酷! Marshal.PtrToStructure() 抛出枚举类型(可能是因为你不能在枚举上使用[StructLayout]?)。对于枚举,您可以使用typeof(T).GetEnumUnderlyingType(),它可以工作。
【解决方案2】:

这是 Jesper 代码的略微修改版本:

public static T? ReadStructure<T>(this Stream stream) where T : struct
{
    if (stream == null)
        return null;

    int size = Marshal.SizeOf(typeof(T));
    byte[] bytes = new byte[size];
    if (stream.Read(bytes, 0, size) != size) // can't build this structure!
        return null;

    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        handle.Free();
    }
}

它成功地处理了 EOF 情况,因为它返回了一个可以为空的类型。

【讨论】:

    【解决方案3】:

    在 C# 中可以做类似的事情,但是您必须将大量属性应用于结构,以便您准确控制它在内存中的布局方式。默认情况下,JIT 编译器控制结构成员在内存中的布局方式,这通常意味着考虑到速度和内存使用情况,它们会被重新排列和填充以获得最有效的布局。

    最简单的方法通常是使用 BinaryReader 读取文件中结构的单独成员,并将值放在类中的属性中,即手动将数据反序列化到类实例中。

    通常读取文件是该操作的瓶颈,因此读取单独成员的小开销不会明显影响性能。

    【讨论】:

    • 听起来很合理。性能不是这里的主要问题,我只是觉得有点不方便。
    • 再想一想,我不想使用循环,只是为了读取一个数组。
    • @B_old:编写几行代码来一次读取一个值要比为结构的所有成员获取正确的属性以保证放置要容易得多在文件排列时完全在内存中输出。无论您选择何种解决方案,您都不会以某种形式使用循环。
    【解决方案4】:

    C# 中没有类似的方法。此外,由于其不可移植性,这是不推荐使用的序列化方式。请改用http://www.codeproject.com/KB/cs/objserial.aspx

    【讨论】:

    • 什么情况下不便携?我正在使用该 c++ 代码在 x86 和 x64 中读取相同的数据,并且似乎工作正常。
    • 如果您在一个平台(例如 x86)中写入数据并在另一个平台(64)中读取数据,那么您可能会遇到问题。
    • 这正是我不明白的,因为我正在这样做。您是否有链接可以更详细地解释该问题?
    • 不,我没有全面解释的链接,抱歉。但是我知道内存布局在不同的平台上是不同的,即使在使用不同的编译器参数时也是如此。 “内存中的字段必须按照它们在源代码中出现的顺序出现”或“long 在所有平台上用 32 位表示”或“字段在任何地方都以 32 位数据包对齐”没有确切的标准,并且不能成为这样的标准。在 x86 和 x64 中成功使用该 C++ 代码意味着您很幸运。尝试使用编译器密钥或尝试为 ARM 编译。
    • 它更像是一个编译器实现,而不是 x86 vs x64。
    【解决方案5】:

    只是为了详细说明 Guffa 和 jesperll 的回答,这里是使用基本相同的 ReadStruct 方法(只是不作为扩展方法)读取 ASF (WMV/WMA) 文件的文件头的示例

    MemoryStream ms = new MemoryStream(headerData);
    AsfFileHeader asfFileHeader = ReadStruct<AsfFileHeader>(ms);
    
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    internal struct AsfFileHeader
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] 
        public byte[] object_id;
        public UInt64 object_size;
        public UInt32 header_object_count;
        public byte r1;
        public byte r2;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-16
      • 1970-01-01
      相关资源
      最近更新 更多