【问题标题】:Read binary file into a struct将二进制文件读入结构
【发布时间】:2008-08-05 14:28:57
【问题描述】:

我正在尝试使用 C# 读取二进制数据。我拥有我想要阅读的文件中有关数据布局的所有信息。我能够“逐块”读取数据,即获取前 40 个字节的数据将其转换为字符串,然后获取接下来的 40 个字节。

由于数据至少有三个略有不同的版本,我想直接将数据读入一个结构体。感觉比“逐行”阅读要正确得多。

我尝试了以下方法,但无济于事:

StructType aStruct;
int count = Marshal.SizeOf(typeof(StructType));
byte[] readBuffer = new byte[count];
BinaryReader reader = new BinaryReader(stream);
readBuffer = reader.ReadBytes(count);
GCHandle handle = GCHandle.Alloc(readBuffer, GCHandleType.Pinned);
aStruct = (StructType) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(StructType));
handle.Free();

流是一个打开的 FileStream,我已经开始从中读取。使用Marshal.PtrToStructure 时,我得到一个AccessViolationException。

由于我对文件末尾的数据不感兴趣,因此该流包含的信息比我尝试读取的要多。

结构体的定义如下:

[StructLayout(LayoutKind.Explicit)]
struct StructType
{
    [FieldOffset(0)]
    public string FileDate;
    [FieldOffset(8)]
    public string FileTime;
    [FieldOffset(16)]
    public int Id1;
    [FieldOffset(20)]
    public string Id2;
}

示例代码已从原始代码更改为更短。

如何将文件中的二进制数据读取到结构中?

【问题讨论】:

    标签: c# struct io binaryfiles


    【解决方案1】:

    问题是结构中的 string。我发现像 byte/short/int 这样的编组类型不是问题;但是当您需要编组为诸如字符串之类的复杂类型时,您需要您的结构显式地模仿非托管类型。您可以使用 MarshalAs 属性来执行此操作。

    对于您的示例,以下内容应该有效:

    [StructLayout(LayoutKind.Explicit)]
    struct StructType
    {
        [FieldOffset(0)]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
        public string FileDate;
    
        [FieldOffset(8)]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
        public string FileTime;
    
        [FieldOffset(16)]
        public int Id1;
    
        [FieldOffset(20)]
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 66)] //Or however long Id2 is.
        public string Id2;
    }
    

    【讨论】:

      【解决方案2】:

      这是我正在使用的。
      这对我来说成功地读取了可移植可执行格式。
      这是一个通用函数,所以 T 是你的 struct 类型。

      public static T ByteToType<T>(BinaryReader reader)
      {
          byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));
      
          GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
          T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
          handle.Free();
      
          return theStructure;
      }
      

      【讨论】:

        【解决方案3】:

        正如 Ronnie 所说,我会使用 BinaryReader 并单独读取每个字段。我找不到包含此信息的文章的链接,但据观察,如果结构包含少于 30-40 个左右的字段,则使用 BinaryReader 读取每个单独的字段可能比 Marshal.PtrToStruct 更快。找到该文章的链接后,我会发布它。

        文章链接在:http://www.codeproject.com/Articles/10750/Fast-Binary-File-Reading-with-C

        在编组结构数组时,PtrToStruct 会更快地占上风,因为您可以将字段计数视为字段 * 数组长度。

        【讨论】:

        【解决方案4】:

        我认为您的代码没有任何问题。

        我想不通,如果您尝试手动操作会怎样?有用吗?

        BinaryReader reader = new BinaryReader(stream);
        StructType o = new StructType();
        o.FileDate = Encoding.ASCII.GetString(reader.ReadBytes(8));
        o.FileTime = Encoding.ASCII.GetString(reader.ReadBytes(8));
        ...
        ...
        ...
        

        也试试

        StructType o = new StructType();
        byte[] buffer = new byte[Marshal.SizeOf(typeof(StructType))];
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        Marshal.StructureToPtr(o, handle.AddrOfPinnedObject(), false);
        handle.Free();
        

        然后在 BinaryReader 中使用 buffer[] 而不是从 FileStream 中读取数据,以查看是否仍然出现 AccessViolation 异常。

        我没有运气使用 BinaryFormatter,我想我必须 有一个匹配的完整结构 准确的文件内容。

        有道理,BinaryFormatter 有自己的数据格式,和你的完全不兼容。

        【讨论】:

          【解决方案5】:

          我没有使用 BinaryFormatter,我想我必须有一个与文件内容完全匹配的完整结构。我意识到最终我对文件内容并不感兴趣,所以我采用了将部分流读取到字节缓冲区然后使用转换它的解决方案

          Encoding.ASCII.GetString()
          

          对于字符串和

          BitConverter.ToInt32()
          

          对于整数。

          稍后我需要能够解析更多文件,但对于这个版本,我只用了几行代码就成功了。

          【讨论】:

            【解决方案6】:

            试试这个:

            using (FileStream stream = new FileStream(fileName, FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                StructType aStruct = (StructType)formatter.Deserialize(filestream);
            }
            

            【讨论】:

            • BinaryFormatter 有自己的二进制数据格式 - 如果您自己读/写数据,这很好。如果您从其他来源获取文件,则无用。
            【解决方案7】:

            直接读入结构是邪恶的——许多 C 程序由于不同的字节顺序、不同的字段编译器实现、打包、字长......而失败。

            您最擅长逐字节进行序列化和反序列化。如果你愿意或者只是习惯 BinaryReader,可以使用内置的东西。

            【讨论】:

            • 我不同意,直接读取结构有时是将数据转换为可用对象的最快方法。如果您正在编写面向性能的代码,这可能非常有用。是的,您必须注意对齐和包装,并确保任何端点机器都使用相同的。
            • 我也不同意。当性能是关键时,或者当您需要二进制 C++/C# 互操作时,编写简单的 structs 是要走的路。
            【解决方案8】:

            我有结构:

            [StructLayout(LayoutKind.Explicit, Size = 21)]
                public struct RecordStruct
                {
                    [FieldOffset(0)]
                    public double Var1;
            
                    [FieldOffset(8)]
                    public byte var2
            
                    [FieldOffset(9)]
                    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]
                    public string String1;
                }
            }
            

            我收到“不正确对齐或被非对象重叠”。 基于此,我发现: https://social.msdn.microsoft.com/Forums/vstudio/en-US/2f9ffce5-4c64-4ea7-a994-06b372b28c39/strange-issue-with-layoutkindexplicit?forum=clr

            好的。我想我明白这里发生了什么。这似乎是 问题与数组类型(它是一个对象 type) 必须存储在内存中的 4 字节边界处。然而,什么 你真正想做的是分别序列化这 6 个字节。

            我认为问题在于 FieldOffset 和序列化之间的混合 规则。我认为 structlayout.sequential 可能对你有用, 因为它实际上并没有修改 结构体。我认为 FieldOffset 实际上是在修改内存 类型的布局。这会导致问题,因为 .NET 框架 要求对象引用在适当的边界上对齐(它 似乎)。

            所以我的结构被定义为显式的:

            [StructLayout(LayoutKind.Explicit, Size = 21)]
            

            因此我的字段已指定

            [FieldOffset(<offset_number>)]
            

            但是当您将结构更改为顺序时,您可以摆脱这些偏移量,错误就会消失。比如:

            [StructLayout(LayoutKind.Sequential, Size = 21)]
                public struct RecordStruct
                {
                    public double Var1;
            
                    public byte var2;
            
                    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]
                    public string String1;
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-04-28
              • 1970-01-01
              • 2019-05-14
              • 2021-11-14
              • 1970-01-01
              相关资源
              最近更新 更多