我正在尝试使用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 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);
} |