Как читать байтовые блоки в структуру - PullRequest
1 голос
/ 03 августа 2011

У меня есть этот файл ресурсов, который мне нужно обработать, который упаковывает набор файлов.

Во-первых, в файле ресурсов перечислены все файлы, содержащиеся в нем, а также некоторые другие данные, например, в этой структуре:

struct FileEntry{
     byte Value1;
     char Filename[12];
     byte Value2;
     byte FileOffset[3];
     float whatever;
}

Так что мне нужно будет прочитать блоки именно этого размера.

Я использую функцию Read из FileStream, но как я могу указать размер структуры?Я использовал:

int sizeToRead = Marshal.SizeOf(typeof(Header));

и затем передал это значение для чтения, но тогда я могу только прочитать набор байтов [], который я не знаю, как преобразовать в указанные значения (ну, я знаю, какчтобы получить однобайтовые значения ... но не остальные).

Также мне нужно указать небезопасный контекст, который я не знаю, является ли он правильным или нет ...

Мне кажется, что чтение байтовых потоков сложнее, чем я думал в .NET:)

Спасибо!

Ответы [ 5 ]

7 голосов
/ 03 августа 2011

Предполагая, что это C #, я бы не стал создавать структуру как тип FileEntry.Я бы заменил char [20] на строки и использовал бы BinaryReader - http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx для чтения отдельных полей.Вы должны прочитать данные в том же порядке, в котором они были записаны.

Что-то вроде:

class FileEntry {
     byte Value1;
     char[] Filename;
     byte Value2;
     byte[] FileOffset;
     float whatever;
}

  using (var reader = new BinaryReader(File.OpenRead("path"))) {
     var entry = new FileEntry {
        Value1 = reader.ReadByte(),
        Filename = reader.ReadChars(12) // would replace this with string
        FileOffset = reader.ReadBytes(3),
        whatever = reader.ReadFloat()           
     };
  }

Если вы настаиваете на наличии структуры, вы должны сделать вашу структуру неизменной и создать конструктораргументы для каждого вашего поля.

5 голосов
/ 03 августа 2011

Если вы можете использовать небезопасный код:

unsafe struct FileEntry{
     byte Value1;
     fixed char Filename[12];
     byte Value2;
     fixed byte FileOffset[3];
     float whatever;
}

public unsafe FileEntry Get(byte[] src)
{
     fixed(byte* pb = &src[0])
     {
         return *(FileEntry*)pb;
     } 
}

Фиксированное ключевое слово встраивает массив в структуру. Поскольку это исправлено, это может вызывать проблемы с сборкой мусора, если вы постоянно их создаете и никогда не отпускаете. Имейте в виду, что постоянные размеры - это n * sizeof (t). Таким образом, Filename [12] выделяет 24 байта (каждый символ равен 2 байта в Unicode), а FileOffset [3] выделяет 3 байта. Это имеет значение, если вы не имеете дело с данными Юникода на диске. Я бы порекомендовал изменить его на byte [] и преобразовать структуру в пригодный для использования класс, в который можно преобразовать строку.

Если вы не можете использовать небезопасный, вы можете использовать весь подход BinaryReader:

public unsafe FileEntry Get(Stream src)
{
     FileEntry fe = new FileEntry();
     var br = new BinaryReader(src);
     fe.Value1 = br.ReadByte();
     ...
}

Небезопасный путь почти мгновенен, гораздо быстрее, особенно когда вы конвертируете много структур одновременно. Вопрос в том, хотите ли вы использовать небезопасно. Я рекомендую использовать небезопасный метод, только если вам абсолютно нужно повышение производительности.

3 голосов
/ 22 мая 2013

Основываясь на этой статье , только я сделал ее обобщенной, это как маршалировать данные непосредственно в структуру.Очень полезно для длинных типов данных.

public static T RawDataToObject<T>(byte[] rawData) where T : struct
{
    var pinnedRawData = GCHandle.Alloc(rawData,
                                       GCHandleType.Pinned);
    try
    {
        // Get the address of the data array
        var pinnedRawDataPtr = pinnedRawData.AddrOfPinnedObject();

        // overlay the data type on top of the raw data
        return (T) Marshal.PtrToStructure(pinnedRawDataPtr, typeof(T));
    }
    finally
    {
        // must explicitly release
        pinnedRawData.Free();
    }
}

Пример использования:

[StructLayout(LayoutKind.Sequential)]
public struct FileEntry
{
    public readonly byte Value1;

    //you may need to play around with this one
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    public readonly string Filename;

    public readonly byte Value2;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public readonly byte[] FileOffset;

    public readonly float whatever;
}

private static void Main(string[] args)
{
    byte[] data =;//from file stream or whatever;
    //usage
    FileEntry entry = RawDataToObject<FileEntry>(data);
}
2 голосов
/ 03 августа 2011

Упаковка FileStream с BinaryReader даст вам выделенные Read*() методы для примитивных типов: http://msdn.microsoft.com/en-us/library/system.io.binaryreader.aspx

Из моей головы, вы, вероятно, могли бы пометить свой struct как [StructLayout(LayoutKind.Sequential)] (чтобы обеспечить правильное представление в памяти) и использовать указатель в блоке unsafe, чтобы фактически заполнить стиль struct C.Переход на unsafe не рекомендуется, если он вам действительно не нужен (взаимодействие, тяжелые операции, такие как обработка изображений и т. Д.).

0 голосов
/ 17 ноября 2016

Не полный ответ (я думаю, что он был рассмотрен), но конкретное примечание к имени файла:

Тип Char, вероятно, не является однобайтовым в C #, поскольку символы .NetUnicode, то есть они поддерживают символьные значения далеко за пределами 255, поэтому интерпретация данных вашего имени файла как массива Char[] создаст проблемы.Поэтому первым шагом, безусловно, следует прочитать это как Byte[12], а не Char[12].

Прямое преобразование из байтового массива в массив char также не рекомендуется, хотя, поскольку в двоичных индексах, подобных этому, имена файлов, которыена короче , чем разрешенные 12 символов, вероятно, будут дополнены 0 байтами, поэтому прямое преобразование приведет к строке длиной 12 символов и может заканчиваться этими нулевыми символами.

Однакопростое обрезание этих нулей не рекомендуется, поскольку системы чтения таких данных обычно просто считывают до первого обнаруженного нуля, а данные за , которые в массиве могут фактически содержать мусор, если система записи не 'не пытайтесь специально очистить свой буфер нулями, прежде чем помещать в него строку.Это то, о чем многие программы не беспокоятся, поскольку они предполагают, что система чтения все равно будет интерпретировать строку только до первого нуля.

Итак, если предположить, что это действительно такой типичный конец с нулем (C-style), сохраняемая в кодировке текста по одному байту на символ (например, ASCII или Win-1252), второй шаг - обрезать строку в первом нуле.Вы можете легко сделать это с помощью функции TakeWhile Линка.Затем третьим и последним шагом является преобразование полученного байтового массива в строку с какой бы то ни было текстовой кодировкой в ​​один байт на символ, с которой он написан:

public String StringFromCStringArray(Byte[] readData, Encoding encoding)
{
    return encoding.GetString(readData.TakeWhile(x => x != 0).ToArray());
}

Как я уже сказал, кодировка будетвероятно, это что-то вроде чистого ASCII, к которому можно получить доступ из Encoding.ASCII, или из Windows-1252, стандартной кодировки текста Windows / Западной Европы Windows, которую вы можете получить с помощью Encoding.GetEncoding("Windows-1252").

...