59

我正在尝试使用 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,我已经开始从中读取。使用时我得到一个AccessViolationException 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;
}

示例代码从原始代码更改为使此问题更短。

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

4

7 回答 7

33

问题是结构中的字符串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;
}
于 2008-08-21T19:02:50.507 回答
16

这是我正在使用的。
这对我阅读 Portable Executable Format 很成功。
这是一个通用函数,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;
}
于 2010-11-02T02:40:14.273 回答
6

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

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

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

于 2010-05-06T01:04:40.047 回答
3

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

就在我的脑海里,如果你尝试手动做呢?它有效吗?

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

于 2008-08-05T15:31:43.330 回答
3

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

Encoding.ASCII.GetString()

对于字符串和

BitConverter.ToInt32()

对于整数。

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

于 2008-08-06T09:03:28.277 回答
0

尝试这个:

using (FileStream stream = new FileStream(fileName, FileMode.Open))
{
    BinaryFormatter formatter = new BinaryFormatter();
    StructType aStruct = (StructType)formatter.Deserialize(filestream);
}
于 2008-08-05T14:56:10.937 回答
0

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

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

于 2008-09-23T21:43:29.523 回答