При работе с внешними источниками данных через COM-объекты, например, при получении данных из баз данных через ADODB, часто возникает задача обработки бинарных полей (изображений, файлов, документов). Стандартный подход — сохранить полученный массив байт во временный файл и затем прочитать его в 1С как объект ДвоичныеДанные. Однако этот метод создает лишние операции ввода-вывода, что замедляет работу и "замусоривает" дисковое пространство. Для настройки базового процесса получения данных можно использовать руководство по подключению и получению данных из SQL в 1С.
Рассмотрим, как избавиться от временных файлов и выполнить преобразование `COMSafeArray` в `ДвоичныеДанные` полностью в оперативной памяти.
Проанализируем исходную ситуацию. У нас есть код, который получает данные через ADODB, и для бинарных полей использует временный файл:
Соединение = Новый COMОбъект("ADODB.Connection");
Команда = Новый COMОбъект("ADODB.Command");
Команда.CommandText = "ТекстЗапроса";
НаборЗаписей = Команда.Execute();
ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
Поток = Новый COMОбъект("ADODB.Stream");
Поток.Type = 1; // adTypeBinary
Поток.Mode = 3; // adModeReadWrite
Поток.Open();
Пока НЕ НаборЗаписей.EOF Цикл
НСтр = ВнешниеДанные.Добавить();
Для Каждого Колонка Из НаборЗаписей.Fields Цикл
Значение = НаборЗаписей.Fields(Колонка.Name).Value;
Если ТипЗнч(Значение) = Тип("COMSafeArray") Тогда
// Проблемный участок кода с использованием временного файла
Поток.Write(Значение);
Поток.SaveToFile(ИмяВременногоФайла, 2); // 2 = adSaveCreateOverWrite
НСтр[Колонка.Name] = Новый ДвоичныеДанные(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла); // Не забываем чистить за собой
Иначе
НСтр[Колонка.Name] = Значение;
КонецЕсли;
КонецЦикла;
НаборЗаписей.MoveNext();
КонецЦикла;
Поток.Close();
Соединение.Close();
Основная задача — заменить блок с Поток.Write(), Поток.SaveToFile() и созданием ДвоичныхДанных из файла на более эффективное решение, работающее в памяти.
Первая мысль, которая может прийти в голову, — это выгрузить COMSafeArray в массив 1С и собрать из него ДвоичныеДанные побайтово. Например, с помощью БуферДвоичныхДанных.
Однако этот подход имеет критический недостаток — низкую производительность. Причина кроется в механизме взаимодействия 1С с COM-объектами (маршалинг). Каждое обращение к элементу массива COMSafeArray из кода 1С — это отдельный вызов COM, который требует переключения контекста выполнения. Если ваш массив содержит сотни тысяч или миллионы байт, это приведет к такому же количеству вызовов, что займет огромное, неприемлемое время. Файл размером 500 КБ может обрабатываться десятки секунд или даже минут.
Поэтому нам нужны методы, которые выполняют преобразование "оптом", передавая весь массив за один вызов.
Этот способ является наиболее производительным и универсальным для работы в памяти. Мы используем стандартный COM-объект Microsoft.XMLDOM, который умеет быстро кодировать бинарные данные в строку формата Base64. А 1С, в свою очередь, умеет так же быстро преобразовывать строку Base64 обратно в двоичные данные. При возникновении ошибок инициализации или регистрации необходимых COM-компонентов в Windows полезно использовать инструкцию по устранению проблем с COM-объектами.
Алгоритм действий:
MSXML2.DOMDocument.bin.base64.COMSafeArray в свойство nodeTypedValue элемента. COM-объект сам выполнит всю работу по кодированию.text элемента.ПолучитьДвоичныеДанныеИзСтрокиBase64() для финального преобразования.Посмотрим, как изменится наш код:
// Создаем объекты для конвертации один раз перед циклом
XMLДокумент = Новый COMОбъект("MSXML2.DOMDocument.6.0");
XMLElement = XMLДокумент.CreateElement("root");
XMLElement.dataType = "bin.base64";
// ... код подключения к ADODB ...
Пока НЕ НаборЗаписей.EOF Цикл
НСтр = ВнешниеДанные.Добавить();
Для Каждого Колонка Из НаборЗаписей.Fields Цикл
Значение = НаборЗаписей.Fields(Колонка.Name).Value;
Если ТипЗнч(Значение) = Тип("COMSafeArray") Тогда
// Эффективное преобразование в памяти
XMLElement.nodeTypedValue = Значение;
СтрокаBase64 = XMLElement.text;
НСтр[Колонка.Name] = ПолучитьДвоичныеДанныеИзСтрокиBase64(СтрокаBase64);
Иначе
НСтр[Колонка.Name] = Значение;
КонецЕсли;
КонецЦикла;
НаборЗаписей.MoveNext();
КонецЦикла;
// ... код закрытия соединений ...
Преимущества этого метода:
Если у вас есть возможность изменять SQL-запрос, который отправляется в базу данных, то самым лучшим решением будет выполнить преобразование в Base64 прямо на стороне SQL-сервера. В этом случае ADODB вернет вам уже готовую текстовую строку, а не массив байт, что максимально упростит код в 1С.
Пример для MS SQL Server:
В MS SQL Server можно использовать FOR XML PATH для кодирования бинарного поля в Base64.
-- Старый запрос:
-- ВЫБРАТЬ BinaryField FROM MyTable
-- Новый, оптимизированный запрос:
SELECT
(SELECT BinaryField AS '*' FOR XML PATH(''), BINARY BASE64) AS BinaryFieldBase64
FROM MyTable
После такого изменения запроса, в 1С вы будете получать обычную строку, и код станет тривиальным:
// ...
Значение = НаборЗаписей.Fields("BinaryFieldBase64").Value;
НСтр[Колонка.Name] = ПолучитьДвоичныеДанныеИзСтрокиBase64(Значение);
// ...
Этот подход является идеальным, так как переносит всю тяжелую работу на сервер баз данных, который для таких операций оптимизирован гораздо лучше, и минимизирует объем передаваемых данных в сложном формате.
Для преобразования COMSafeArray в ДвоичныеДанные без использования временных файлов есть два эффективных пути:
Категорически не рекомендуется использовать побайтовый перебор массива в цикле из-за катастрофического падения производительности на больших объемах данных.