Читать бинарный файл в структуру
Я пытаюсь читать двоичные данные с помощью 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, из которого я начал читать. Я получаю 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;
}
Код примеров изменен с оригинального, чтобы этот вопрос был короче.
Как бы я прочитал двоичные данные из файла в структуру?
8 ответов
Проблема в строке 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;
}
Вот что я использую.
Это успешно сработало для меня для чтения 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;
}
Как сказал Ронни, я бы использовал BinaryReader и читал каждое поле отдельно. Я не могу найти ссылку на статью с этой информацией, но было замечено, что использование BinaryReader для чтения каждого отдельного поля может быть быстрее, чем Marshal.PtrToStruct, если структура содержит менее 30-40 или около того полей. Я выложу ссылку на статью, когда найду ее.
Ссылка на статью находится по адресу: http://www.codeproject.com/Articles/10750/Fast-Binary-File-Reading-with-C
При маршалинге массива структур PtrToStruct быстрее одерживает верх, потому что вы можете считать количество полей как поля * длины массива.
Я не вижу никаких проблем с вашим кодом.
просто у меня в голове, что если вы попытаетесь сделать это вручную? это работает?
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 вместо чтения данных из FileStream, чтобы увидеть, получаете ли вы по-прежнему исключение AccessViolation.
Мне не повезло с использованием BinaryFormatter, я думаю, у меня должна быть полная структура, которая точно соответствует содержимому файла.
Это имеет смысл, BinaryFormatter имеет свой собственный формат данных, полностью несовместимый с вашим.
Мне не повезло с использованием BinaryFormatter, я думаю, у меня должна быть полная структура, которая точно соответствует содержимому файла. Я понял, что, в конце концов, я все равно не очень заинтересовался содержимым файла, поэтому я решил найти часть потока в байтовый буфер и затем преобразовать его, используя
Encoding.ASCII.GetString()
для строк и
BitConverter.ToInt32()
для целых чисел.
Позже мне нужно будет разобрать больше файлов, но для этой версии мне не хватило всего пары строк кода.
Попробуй это:
using (FileStream stream = new FileStream(fileName, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
StructType aStruct = (StructType)formatter.Deserialize(filestream);
}
У меня была структура:
[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
ХОРОШО. Кажется, я понимаю, что здесь происходит. Похоже, проблема связана с тем, что тип массива (который является типом объекта) должен храниться в памяти на границе 4 байт. Однако на самом деле вы пытаетесь сериализовать 6 байтов отдельно.
Я думаю, что проблема заключается в сочетании правил FieldOffset и сериализации. Я думаю, что structlayout.sequential может сработать для вас, поскольку на самом деле он не изменяет представление структуры в памяти. Я думаю, что FieldOffset на самом деле изменяет макет типа в памяти. Это вызывает проблемы, потому что платформа .NET требует, чтобы ссылки на объекты были выровнены по соответствующим границам (кажется).
Итак, моя структура была определена как явная с помощью:
[StructLayout(LayoutKind.Explicit, Size = 21)]
и, таким образом, мои поля указали
[FieldOffset(<offset_number>)]
но когда вы измените свою структуру на Sequential, вы можете избавиться от этих смещений, и ошибка исчезнет. Что-то типа:
[StructLayout(LayoutKind.Sequential, Size = 21)]
public struct RecordStruct
{
public double Var1;
public byte var2;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)]
public string String1;
}
}
Чтение прямо в структуры - это зло - многие программы на C упали из-за разных порядков байтов, разных реализаций полей компилятором, упаковки, размера слова.......
Вы лучше всего сериализуете и десериализуете побайтово. Используйте встроенный материал, если хотите, или просто привыкните к BinaryReader.