Работа с двоичными данными

image_pdfimage_print
Печать (Ctrl+P)

16.3. Работа с двоичными данными

16.3.1. Общая информация

При реализации прикладных решений возможны ситуации, когда необходимо анализировать различные двоичные данные. Например, требуется по сигнатуре определить тип файла или выполнить какие-либо манипуляции с картинкой. Для работы с двоичными данными «1С:Предприятие» предоставляет специальные программные интерфейсы. Далее будут рассмотрены возможности по работе с двоичными данными.
Вся работа с двоичными данными базируется на понятии потока. Поток – это логическое обобщение произвольного (в общем случае) источника данных (объект Поток). Система не предоставляет возможности создать самостоятельный объект Поток, который не связан с каким-либо источником. Но существуют производные объекты, которые можно создать – это поток, связанный с файлом на диске (объект ФайловыйПоток) или поток, созданный в памяти (объект ПотокВПамяти). Поток позволяет как читать данные, так и записывать их. Для определения возможности выполнения тех или иных операций, у потока (и производных объектов) существуют специальные методы, позволяющие определить, какие
операции доступны с данным потоком (методы ДоступнаЗапись(), ДоступноЧтение(), ДоступноИзменениеПозиции()).
Если необходимо работать с потоком на более высоком уровне, в частности, выполнять чтение/запись таких данных, как число (разной разрядности) или строка, то для этого предназначены объекты ЧтениеДанных/ЗаписьДанных. С помощью этих объектов имеется возможность более структурировано подходить к двоичным данным, расположенным в потоке. Так, например, зная формат какого-либо файла, можно достаточно комфортно выполнять чтение такого файла, получая из заголовков нужные данные (которые, как правило, представлены типами число и строка), пропуская не нужные блоки данных и загружая для обработки нужные.
Общую схему работы с двоичными данными можно представить следующим образом:

  1.  Выполняется получение потока
  2.  Создается объект ЧтениеДанных или ЗаписьДанных.
  3.  С помощью объекта, созданного в п.2, выполняются требуемые действия.
  4.  Закрывается объект, созданный в п.2.
  5.  Если никаких операций больше выполнять не требуется – закрывается поток, полученный в п.1.
  6.  Если требуется продолжить работу с потоком, то можно выполнить установку новой позиции в потоке (если эта операция поддерживается) и продолжить работу, начиная с п.2.

Стоит отметить, что имеется возможность объединить пп.1 и 2. Другими словами, система предоставляет возможность создания объектов ЧтениеДанных/ЗаписьДанных сразу из, например, объекта ДвоичныеДанные.
Для выполнения различных операций с двоичными данными система предоставляет возможность получить некоторую часть потока в качестве обособленного фрагмента с произвольным (побайтовым) доступом (объект БуферДвоичныхДанных). Размер буфера задается при создании и не может быть изменен в дальнейшем. При работе с буфером двоичных данных имеется возможность работать с числами разной разрядности как с
единым целым. При этом имеется возможность указания порядка следования байтов в словах: «младший-старший» (little endian) или «старший-младший» (big endian). Также имеется возможность разбиения одного буфера на несколько и объединения нескольких буферов двоичных данных в один результирующий буфер.
Важно отметить, что работа с буфером двоичных данных позволяет существенно упростить реализацию в том случае, если работа с двоичными данными реализуется на стороне клиентского приложения в асинхронном режиме. В этом случае чтение данных в буфер будет выполняться асинхронной операцией, а работа с данными буфера является синхронной.
Работа с двоичными данными доступна на стороне клиентского (включая веб-клиент) приложения и на стороне сервера, а также в синхронной и асинхронной схемах работы. Дальнейшие примеры будут использовать синхронную схему работы.

16.3.2. Чтение двоичных данных

В качестве примера чтения двоичных данных будет рассмотрена задача определения корректного формата файла, который выбрали в системе для дальнейшего использования. В роли проверяемого файла будет использоваться .wav-файл с аудио-данными. Для хранения .wav-файлов используется Resource Interchange File Format (RIFF), описание которого приведено по ссылке:

https://msdn.microsoft.com/enus/library/windows/desktop/ee415713.aspx   (на английском языке). Для примера чтения будут использоваться следующие данные о формате:
1. первые 4 байта файла содержат идентификатор формата: RIFF.
2. следующие 4 байта содержат размер собственно аудио данных с порядком следования байт little-endian.
3. следующие 4 байта содержат текстовый тип используемых данных: WAVE.
Для выполнения этих действий потребуется следующий код на встроенном языке:

Чтение = Новый ЧтениеДанных(ИмяФайла, , ПорядокБайтов.LittleEndian);
ФорматФайла = Чтение.ПрочитатьСимволы(4);
РазмерДанных = Чтение.ПрочитатьЦелое32();
ТипФайла = Чтение.ПрочитатьСимволы(4);
Если ФорматФайла <> “RIFF” Тогда
Сообщить(“Это не файл формата RIFF”);
Возврат;
КонецЕсли;
Если ТипФайла = “WAVE” Тогда
Сообщить(“Это WAV-файл с данными, размером ” + РазмерДанных + ” байт”);
Иначе
Сообщить(“Это не WAV-файл”);
Возврат;
КонецЕсли;

Рассмотрим пример более подробно.
Вначале открывается файл, имя которого содержится в переменной ИмяФайла, файл открывается для чтения (РежимОткрытияФайла.Открыть), из файла будут только читать (ДоступКФайлу.Чтение) и для чтения будет использоваться буфер размером 16 байт.
Затем формируется поток, предназначенный для чтения данных, который будет иметь порядок следования байтов «младший-старший» для данных типа Число. Затем из получившегося потока считывается 4 символа, 32-разрядное целое и еще 4 символа. Получившиеся данные анализируются и по результатам анализа принимается решение о том, является выбранный файл .wav-файлом или нет.

16.3.3. Запись двоичных данных

Запись двоичных данных в файл, в простейшем случае, выполняется следующим образом:

Запись = Новый ЗаписьДанных(ИмяФайла);
Для Индекс=0 По 255 Цикл
Запись.ЗаписатьБайт(Индекс);
КонецЦикла;
Запись.Закрыть();

Данный пример выполняет запись в файл последовательности байтов от 0 до 255 (0xFF в 16-ричной системе). Это самый простой вариант записи.
Также можно использовать способ, аналогичный способу чтения, рассмотренному в предыдущем примере, когда получается файловый поток и в этот файловый поток выполняется запись данных.

16.3.4. Работа с буфером двоичных данных

Как уже было сказано выше, буфер двоичных данных предоставляет удобный способ по манипуляции фрагментами двоичных данных.
Поддерживается не только чтение данных, но и запись.
В качестве примера будет рассмотрен разбор заголовка RIFF-файла из примера чтения данных (см. здесь). Для построения примера будут использованы ровно та же информация о формате файла. Таким образом, необходимо прочитать из исходного файла буфер размером с заголовок файла. Заголовок состоит из трех 4-байтовых полей. Таким образом, необходимо прочитать 12 байт.

Буфер = Новый БуферДвоичныхДанных(12);
Файл = ФайловыеПотоки.Открыть(КаталогВременныхФайлов() + “Windows Logon.wav”, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);
Файл.Прочитать(Буфер, 0, 12);
Размер = Буфер.ПрочитатьЦелое32(4);
ПотокСтроки = Новый ПотокВПамяти(Буфер);
ПотокСтроки.Перейти(0, ПозицияВПотоке.Начало);
ЧтениеСтроки = Новый ЧтениеДанных(ПотокСтроки);
ФорматФайла = ЧтениеСтроки.ПрочитатьСимволы(4, “windows-1251”);
ЧтениеСтроки.Закрыть();
ПотокСтроки.Перейти(8, ПозицияВПотоке.Начало);
ЧтениеСтроки = Новый ЧтениеДанных(ПотокСтроки);
ТипФайла = ЧтениеСтроки.ПрочитатьСимволы(4, “windows-1251”);
ЧтениеСтроки.Закрыть();

Процесс получения данных в буфер двоичных данных не представляет из себя ничего особенного. Дальнейшие операции требуют некоторых комментариев. Чтение чисел любой поддерживаемой разрядности возможно из любой позиции буфера. В данном примере Буфер.ПрочитатьЦелое32(4); означает чтение 32-разрядного целого числа, начиная с 4 байта буфера. Таким образом, если требуется прочитать несколько чисел, расположенных в разных местах буфера, это можно сделать без прямого позиционирования в этом буфере.
Чтение строки, однако, не поддерживается буфером двоичных данных. Поэтому следует воспользоваться объектом, который это позволяет сделать: ЧтениеДанных. Объект ЧтениеДанных не может быть создан на основании буфера двоичных данных. Но на основании буфера двоичных данных можно создать поток, который является универсальным посредником между физическим местом хранения информации (файл, буфер двоичных данных) и высокоуровневым объектом, позволяющим работать с этими данными.
Когда создается объект ЧтениеДанных на основании какого-либо потока, он начинает читать данные с той позиции, которая в данный момент установлена в потоке. Поэтому в примере вначале происходит установка позиции в потоке, а потом создается объект ЧтениеДанных и выполняется чтение нужного количества символов. Подробное описание разницы между числом байтов и символов при чтении строк см. следующий раздел 16.3.5

16.3.5. Особенности использования

При использовании двоичных данных следует учитывать особенности работы с данными типа Строка. Особенность заключается в том, что длина строки, которую возвращает функция глобального контекста СтрДлина(), измеряется в символах. В символах же следует указывать размеры читаемых/записываемых данных в методах записи/чтения строк в объектах работы с двоичными данными (ПрочитатьСимволы(),
ПрочитатьСтроку(), ЗаписатьСимволы(), ЗаписатьСтроку()). При этом не существует однозначного варианта преобразования длины строки в символах в аналогичный параметр в байтах. В зависимости от содержимого строки и кодировки, это соотношение будет разным. Поэтому при работе с какими-либо структурами данных, в состав которых входят строки переменной длины, следует четко понимать, в каких единицах выражены длины строк.
Если в имеющихся данных длина строки указана в байтах, и строка указана в многобайтовой кодировке переменной длины (например, UTF-8), то с помощью объектов работы с двоичными данными, прочитать такую структуру из файла в данные типа Строка – в общем случае невозможно.
Зато в таком случае можно легко выполнять изменение позиции чтения/записи в файловом потоке. Если длина строки будет указана в символах, то появляется возможность считать такую строку в данные типа Строка, но становится невозможно выполнять изменение позиции чтения/записи в таком потоке.
Для получения длины строки в байтах, можно воспользоваться следующей функцией для преобразования строки в объект ДвоичныеДанные:

Функция ПолучитьДвоичныеДанныеИзСтроки(Знач СтрПараметр, Знач Кодировка = “UTF-8”)
ПотокПамять = Новый ПотокВПамяти;
Писатель = Новый ЗаписьДанных(ПотокПамять);
Писатель.ЗаписатьСтроку(СтрПараметр, Кодировка);
Писатель.Закрыть();
Возврат ПотокПамять.ЗакрытьИПолучитьДвоичныеДанные();
КонецФункции

Собственно размер в байтах можно получить с помощью вызова функции Размер() у объекта ДвоичныеДанные, который получается в результате работы функции.
Не рекомендуется одновременное использование объектов ЧтениеДанных/ЗаписьДанных и потоковых объектов. Если между двумя последовательными операциями чтения из ЧтениеДанных или двумя последовательными операциями записи в ЗаписьДанных происходит изменение позиции в потоке, с которым работают объекты ЧтениеДанных/ЗаписьДанных – генерируется исключение. Так, следующий пример демонстрирует корректное изменение позиции в потоке при записи данных в поток:

Поток = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(Поток);
ЗаписьДанных.ЗаписатьСтроку(“Привет, мир!”);
ЗаписьДанных.Закрыть();
Поток.Перейти(0, ПозицияВПотоке.Начало);
ЗаписьДанных = Новый ЗаписьДанных(Поток);
ЗаписьДанных.ЗаписатьСтроку(“Пока!”);
ЗаписьДанных.Закрыть();
Следующий пример привет к возникновению исключения:

Поток = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(Поток);
ЗаписьДанных.ЗаписатьСтроку(“Привет, мир!”);
Поток.Перейти(0, ПозицияВПотоке.Начало);
// В следующей строке будет сгенернировано исключение
ЗаписьДанных.ЗаписатьСтроку(“Пока!”);
В тоже время, возможны ситуации, когда поведение системы будет некорректно, но никаких ошибок формироваться не будет:

Поток = ПолучитьПоток();
ЧтениеДанных = Новый ЧтениеДанных(Поток);
ТестоваяСтрока = ЧтениеДанных.Прочитать();
ИсходнаяПозиция = Поток.ТекущаяПозиция();
ЗаписьДанных = Новый ЗаписьДанных(Поток);
ЗаписьДанных.ЗаписатьСтроку(“Неожиданная строка”);
ЗаписьДанных.Закрыть();
Поток.Перейти(ИсходнаяПозиция, ПозицияВПотоке.Начало);
// В общем случае невозможно определить, какое значение будет помещено в переменную ТестоваяСтрока2
ТестоваяСтрока2 = ЧтениеДанных.ПрочитатьСтроку();

Описанное в данном разделе поведение вызвано тем, что объекты ЧтениеДанных/ЗаписьДанных используют собственные буфера при работе с потоком. В результате фактическая позиция потока отличается от логической позиции, которая сформирована как результат совершенных операций.
Также не поддерживается одновременное использование объектов ЧтениеДанных и ЗаписьДанных, которые используют для своей работы один поток.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *