关于C#:将二进制文件读入结构

关于C#:将二进制文件读入结构

Read binary file into a struct

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

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

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

1
2
3
4
5
6
7
8
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时得到AccessViolationExceptio n。

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

该结构的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
[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;
}

示例代码已从原始代码更改为简化此问题。

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


问题是结构中的字符串。我发现诸如byte / short / int之类的封送处理类型不是问题;但是,当您需要编组为字符串之类的复杂类型时,则需要您的结构明确模仿非托管类型。您可以使用MarshalAs属性进行此操作。

对于您的示例,以下应该起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[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;
}

这是我正在使用的方法,它对我来说可以成功读取便携式可执行格式,这是一个通用函数,所以T是您的struct类型。

1
2
3
4
5
6
7
8
9
10
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;
}


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

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

在整理结构数组时,PtrToStruct会更快地获得优势,因为您可以将字段数视为字段*数组长度。


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

1
Encoding.ASCII.GetString()

用于字符串和

1
BitConverter.ToInt32()

为整数。

稍后,我将需要能够解析更多文件,但是对于此版本,我只用了几行代码。


我看不到您的代码有任何问题。

就在我脑海中,如果您尝试手动执行该怎么办?它有效吗?

1
2
3
4
5
6
7
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));
...
...
...

也尝试

1
2
3
4
5
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异常。

I had no luck using the
BinaryFormatter, I guess I have to
have a complete struct that matches
the content of the file exactly.

这很有道理,BinaryFormatter具有自己的数据格式,与您的数据格式完全不兼容。


直接读入结构是邪恶的-许多C程序由于字节顺序不同,字段,打包,字长的不同编译器实现而崩溃。

您最好逐字节进行序列化和反序列化。如果需要,请使用内置的东西,或者只是习惯BinaryReader。


尝试这个:

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

推荐阅读