Может ли кто-нибудь сказать мне, как получить массив байтов в структуру прямым способом в C # .NET версии 2? Подобно знакомому fread, найденному в C, до сих пор я не добился большого успеха в чтении потока байтов и автоматическом заполнении структуры. Я видел некоторые реализации, в которых в управляемом коде присутствует фокус-покус указателя с использованием ключевого слова unsafe.
Взгляните на этот образец:
public unsafe struct foobarStruct{
/* fields here... */
public foobarStruct(int nFakeArgs){
/* Initialize the fields... */
}
public foobarStruct(byte[] data) : this(0) {
unsafe {
GCHandle hByteData = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr pByteData = hByteData.AddrOfPinnedObject();
this = (foobarStruct)Marshal.PtrToStructure(pByteData, this.GetType());
hByteData.Free();
}
}
}
Причина, по которой у меня два конструктора в foobarStruct
- Разве не может быть пустого конструктора.
- Передайте блок памяти (как массив байтов) в конструктор при создании экземпляра структуры.
Достаточно ли хороша эта реализация или есть более чистый способ добиться этого?
Изменить: я не хочу использовать интерфейс ISerializable или его реализацию. Я пытаюсь прочитать двоичное изображение, чтобы определить используемые поля и определить его данные с помощью структур PE.
Ответ 1 · рейтинг 10 · Hans Passant
Нет ничего плохого в использовании маршаллера P / Invoke, он небезопасен, и вам не нужно использовать ключевое слово unsafe. Если вы сделаете это неправильно, то получите неверные данные. Это может быть намного проще в использовании, чем явное написание кода десериализации, особенно когда файл содержит строки. Вы не можете использовать BinaryReader.ReadString (), он предполагает, что строка была написана BinaryWriter. Однако убедитесь, что вы объявляете структуру данных с помощью объявления struct, this.GetType () вряд ли сработает хорошо.
Вот общий класс, который заставит его работать для любого объявления структуры:
class StructureReader<T> where T : struct {
private byte[] mBuffer;
public StructureReader() {
mBuffer = new byte[Marshal.SizeOf(typeof(T))];
}
public T Read(System.IO.FileStream fs) {
int bytes = fs.Read(mBuffer, 0, mBuffer.Length);
if (bytes == 0) throw new InvalidOperationException("End-of-file reached");
if (bytes != mBuffer.Length) throw new ArgumentException("File contains bad data");
T retval;
GCHandle hdl = GCHandle.Alloc(mBuffer, GCHandleType.Pinned);
try {
retval = (T)Marshal.PtrToStructure(hdl.AddrOfPinnedObject(), typeof(T));
}
finally {
hdl.Free();
}
return retval;
}
Пример объявления структуры данных в файле:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct Sample {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 42)]
public string someString;
}
Вам нужно будет настроить объявление структуры и атрибуты, чтобы они соответствовали данным в файле. Пример кода, который читает файл:
var data = new List<Sample>();
var reader = new StructureReader<Sample>();
using (var stream = new FileStream(@"c:\temp\test.bin", FileMode.Open, FileAccess.Read)) {
while(stream.Position < stream.Length) {
data.Add(reader.Read(stream));
}
}
Ответ 2 · рейтинг 3 · Jeff Foster
Вероятно, вы захотите использовать BinaryReader, который позволяет читать примитивные типы в двоичной форме.
Создайте MemoryStream из byte[], а затем используйте BinaryReader от этого. Вы должны иметь возможность прочитать структуру и соответствующим образом заполнить свой объект.