提问人:Robert Höglund 提问时间:8/5/2008 最后编辑:EM-CreationsRobert Höglund 更新时间:3/13/2022 访问量:75429
将二进制文件读入结构体
Read binary file into a struct
问:
我正在尝试使用 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。AccessViolationExceptio
Marshal.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;
}
示例代码与原始代码相比进行了更改,以使此问题更短。
如何将文件中的二进制数据读取到结构体中?
答:
试试这个:
using (FileStream stream = new FileStream(fileName, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
StructType aStruct = (StructType)formatter.Deserialize(filestream);
}
评论
我看不出你的代码有任何问题。
只是在我的脑海中,如果你尝试手动完成它怎么办?它有效吗?
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 有自己的数据格式,与您的数据格式完全不兼容。
我没有运气使用 BinaryFormatter,我想我必须有一个与文件内容完全匹配的完整结构。我意识到最终我对文件内容并不感兴趣,所以我采用了将部分流读取到字节缓冲区,然后使用
Encoding.ASCII.GetString()
对于字符串和
BitConverter.ToInt32()
对于整数。
我稍后需要能够解析更多文件,但对于这个版本,我只用了几行代码就逃脱了。
问题出在结构中的字符串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;
}
评论
直接读取结构体是邪恶的 - 由于不同的字节顺序、不同的编译器实现字段、打包、字大小,许多 C 程序都失败了.......
您最好逐个字节地序列化和反序列化。如果您愿意或只是习惯 BinaryReader,请使用内置的东西。
评论
struct
正如 Ronnie 所说,我会使用 BinaryReader 并单独读取每个字段。我找不到包含此信息的文章的链接,但据观察,如果结构包含少于 30-40 个左右的字段,则使用 BinaryReader 读取每个单独的字段可能比 Marshal.PtrToStruct 更快。当我找到这篇文章时,我会发布它。
文章链接在: http://www.codeproject.com/Articles/10750/Fast-Binary-File-Reading-with-C
在封送结构数组时,PtrToStruct 会更快地占据上风,因为您可以将字段计数视为字段 * 数组长度。
评论
这是我正在使用的。
这对我阅读可移植可执行格式很有效。
它是一个泛型函数,所以 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;
}
我有结构:
[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;
}
}
上一个:如何在 C++ 中打开文件?
下一个:如何检查文件锁定?[复制]
评论