将二进制文件读入结构体

Read binary file into a struct

提问人:Robert Höglund 提问时间:8/5/2008 最后编辑:EM-CreationsRobert Höglund 更新时间:3/13/2022 访问量:75429

问:

我正在尝试使用 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,我已开始从中读取。使用时我得到一个 n。AccessViolationExceptioMarshal.PtrToStructure

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

结构体的定义如下:

[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# 结构 IO 二进制文件

评论


答:

0赞 urini 8/5/2008 #1

试试这个:

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

评论

7赞 russau 7/26/2009
BinaryFormatter 有自己的二进制数据格式 - 如果您自己读取/写入数据,这很好。如果您从其他来源获取文件,则没有用。
3赞 lubos hasko 8/5/2008 #2

我看不出你的代码有任何问题。

只是在我的脑海中,如果你尝试手动完成它怎么办?它有效吗?

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 有自己的数据格式,与您的数据格式完全不兼容。

3赞 Robert Höglund 8/6/2008 #3

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

Encoding.ASCII.GetString()

对于字符串和

BitConverter.ToInt32()

对于整数。

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

36赞 Ishmaeel 8/22/2008 #4

问题出在结构中的字符串s。我发现像 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;
}

评论

0赞 Thomas Weller 10/21/2022
看起来只有当字符串的 FieldOffset 正确对齐时才有可能。我在 FieldOffset(1) 上有一个字符串,但我无法让它工作(导致 TypeLoadException)。
0赞 Ronnie 9/24/2008 #5

直接读取结构体是邪恶的 - 由于不同的字节顺序、不同的编译器实现字段、打包、字大小,许多 C 程序都失败了.......

您最好逐个字节地序列化和反序列化。如果您愿意或只是习惯 BinaryReader,请使用内置的东西。

评论

6赞 Joe 2/4/2012
我不同意,直接读取结构体有时是将数据转换为可用对象的最快方法。如果您正在编写面向性能的代码,这可能非常有用。是的,您必须了解对齐和包装,并确保任何端点计算机都将使用相同的方法。
3赞 Dmitri Nesteruk 3/25/2012
我也不同意。当性能是关键时,或者当您需要二进制 C++/C# 互操作时,编写普通的 s 是要走的路。struct
0赞 JamieB 2/20/2023
如果对内置的东西进行了解释,“使用内置的东西”将是一个有用的答案。C# 似乎缺少任何易于使用的内置内容。如果您正在读取的内容被分解为位字段,则额外为真。
6赞 nevelis 5/6/2010 #6

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

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

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

评论

2赞 Neal Stublen 6/11/2010
我只是在读:codeproject.com/KB/files/fastbinaryfileinput.aspx。这是你正在考虑的文章吗?作者指出:“我发现,在大约40个领域中,三种方法的结果几乎是相同的,除此之外,块阅读方法占了上风。
17赞 Sergey 11/2/2010 #7

这是我正在使用的。
这对我阅读可移植可执行格式很有效。
它是一个泛型函数,所以 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;
}
0赞 Kebechet 3/13/2022 #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 Framework 要求对象引用在适当的边界上对齐(它 似乎)。

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

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

因此,我的字段指定了

[FieldOffset(<offset_number>)]

但是当你将结构更改为Sequentional时,你可以去掉这些偏移量,错误就会消失。像这样:

[StructLayout(LayoutKind.Sequential, Size = 21)]
    public struct RecordStruct
    {
        public double Var1;

        public byte var2;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]
        public string String1;
    }
}