При интеграции различных конфигураций 1С часто возникает необходимость автоматизировать перенос документов вместе с прикрепленными к ним файлами. Если перенос реквизитов (строк, чисел, дат) через COM-соединение не вызывает трудностей, то передача бинарных данных (сканов договоров, PDF-файлов, изображений) напрямую невозможна. Это связано с тем, что сложные объекты платформы, такие как ДвоичныеДанные, не могут быть переданы через COM-границу «по значению» — они передаются только в виде ссылок (COM-объектов), которые мгновенно теряют работоспособность при закрытии соединения.
Для успешного решения этой задачи файл необходимо преобразовать в примитивный тип (строку) на стороне базы-источника, передать эту строку как параметр метода COM-объекта, а затем восстановить файл на стороне базы-приемника. В этой статье мы подробно разберем несколько проверенных способов реализации этого механизма, их плюсы, минусы и особенности оптимизации.
Преобразование файла в строку формата Base64 — это самый простой, быстрый и стабильный способ передачи бинарных данных через COM-соединение. Платформа 1С:Предприятие имеет высокопроизводительные встроенные функции для работы с этим форматом, которые работают на уровне платформы без привлечения дополнительных сериализаторов.
Рассмотрим по шагам, как реализовать этот алгоритм.
Шаг 1. Кодирование на стороне источника
В базе-источнике мы считываем файл с диска в объект ДвоичныеДанные, преобразуем его в Base64-строку и передаем в метод COM-соединения:
// Код выполняется в базе-источнике
ПутьКФайлу = "C:\Files\Contract_123.pdf";
Попытка
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
// Преобразуем двоичные данные в Base64-строку
СтрокаПередачи = Base64Строка(ДвоичныеДанныеФайла);
Исключение
ЗаписатьЛогСобытий("Ошибка чтения файла: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Вызываем экспортный метод в базе-приемнике через COM
ИмяФайла = "Договор_123.pdf";
КомСоединение.ПринятьФайлИзИсточника(СтрокаПередачи, ИмяФайла);
Шаг 2. Декодирование на стороне приемника
В общем модуле базы-приемника (который должен иметь флаг «Вызов сервера» или запускаться во внешнем соединении) описываем экспортную процедуру восстановления файла:
// Код выполняется в базе-приемнике (например, 1С:Документооборот)
Процедура ПринятьФайлИзИсточника(СтрокаBase64, ИмяФайла) Экспорт
Попытка
// Восстанавливаем двоичные данные из полученной строки
ДвоичныеДанные = ИзСтрокиBase64(СтрокаBase64);
// Определяем временный путь для сохранения файла на сервере приемника
ИмяВременногоФайла = ПолучитьИмяВременногоФайла(ПолучитьРасширениеФайла(ИмяФайла));
ДвоичныеДанные.Записать(ИмяВременногоФайла);
// Здесь выполняем логику прикрепления файла к документу в базе-приемнике
// Например, создаем элемент справочника "Файлы" или "ВерсииФайлов"
ДобавитьФайлКДокументу(ИмяВременногоФайла, ИмяФайла);
// Удаляем временный файл после прикрепления
УдалитьФайлы(ИмяВременногоФайла);
Исключение
ЗаписатьЛогСобытий("Ошибка сохранения файла в приемнике: " + ОписаниеОшибки());
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Преимущества метода Base64:
Этот метод базируется на стандартных механизмах XML-сериализации платформы. Мы упаковываем двоичные данные в ХранилищеЗначения (что дополнительно сжимает файл), а затем преобразуем хранилище в XML-строку с помощью функции XMLСтрока.
Проанализируем реализацию данного подхода:
Код на стороне базы-источника:
// Читаем файл в двоичные данные
ДвоичныеДанные = Новый ДвоичныеДанные(ПутьКФайлу);
// Помещаем в ХранилищеЗначения со сжатием максимальной степени (9)
Хранилище = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
// Сериализуем хранилище в XML-строку
СтрокаXML = XMLСтрока(Хранилище);
// Передаем полученную строку через COM-соединение
КомОбъект.ПринятьФайлЧерезХранилище(СтрокаXML, "Договор.pdf");
Код на стороне базы-приемника:
Процедура ПринятьФайлЧерезХранилище(СтрокаXML, ИмяФайла) Экспорт
// Восстанавливаем ХранилищеЗначения из XML-строки
Хранилище = XMLЗначение(Тип("ХранилищеЗначения"), СтрокаXML);
// Извлекаем двоичные данные
ДвоичныеДанные = Хранилище.Получить();
Если ДвоичныеДанные <> Неопределено Тогда
ВременныйПуть = КаталогВременныхФайлов() + ИмяФайла;
ДвоичныеДанные.Записать(ВременныйПуть);
// Выполняем дальнейшую обработку файла...
КонецЕсли;
КонецПроцедуры
Важное преимущество: Сжатие данных происходит «на лету» средствами платформы, что может существенно уменьшить объем передаваемой строки для текстовых файлов или несжатых форматов документов.
Сериализатор XDTO позволяет переводить сложные объекты 1С в формат XML и обратно. Данный способ часто применяется в типовых механизмах обмена данными. Однако при работе через COM-соединение между разными конфигурациями здесь могут возникнуть сложности, связанные с несовпадением типов данных или режимов совместимости платформ.
Выясним, как правильно настроить чтение и запись XML, чтобы избежать ошибок десериализации:
// Функция сериализации в XML-строку на стороне источника
Функция ПолучитьXMLСтрокуИзФайла(ПутьКФайлу) Экспорт
ДвоичныеДанные = Новый ДвоичныеДанные(ПутьКФайлу);
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
// Записываем объект через Сериализатор XDTO
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ДвоичныеДанные, НазначениеТипаXML.Явное);
Возврат ЗаписьXML.Закрыть();
КонецФункции
При чтении XML-строки на стороне приемника крайне важно выполнить принудительное чтение буфера (вызвать метод Прочитать() объекта ЧтениеXML). Если этого не сделать, десериализация в некоторых версиях платформы может завершиться ошибкой или вернуть пустой объект:
// Функция десериализации на стороне приемника
Функция ПолучитьФайлИзXMLСтроки(СтрокаXML, ПутьСохранения) Экспорт
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
// Перемещаем указатель на первый элемент XML-документа
ЧтениеXML.Прочитать();
// Десериализуем объект обратно в ДвоичныеДанные
ДвоичныеДанные = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
ДвоичныеДанные.Записать(ПутьСохранения);
КонецФункции
Все вышеописанные методы обладают одной фундаментальной уязвимостью. При сериализации файла в строку (неважно — Base64 или XML) весь файл полностью загружается в оперативную память процесса rphost. Более того, кодирование в Base64 увеличивает размер данных примерно на 33%, а механизмы парсинга XML создают множество временных копий объектов в памяти.
Если вы попытаетесь передать файл размером 100 МБ через COM-соединение в виде одной строки, серверу 1С потребуется выделить до 400-500 МБ оперативной памяти под одну эту операцию. При одновременной работе нескольких пользователей это гарантированно приведет к аварийному завершению процесса rphost с ошибкой «Недостаточно памяти для выполнения операции».
Разберем по шагам, как обойти это ограничение с помощью поблочной передачи (Chunking).
Для стабильной передачи файлов любого объема (от 50 МБ до нескольких гигабайт) мы разделим исходный файл на небольшие части (например, по 2-5 МБ), передадим эти части по очереди через COM, а на стороне приемника склеим их обратно в один файл. Для этого используются встроенные методы платформы РазделитьФайл и ОбъединитьФайлы.
Алгоритм поблочной передачи:
1. Подготовка и отправка частей в источнике:
Процедура ПередатьБольшойФайлЧерезCOM(ПутьКФайлу, ИмяФайла, COMСоединение)
// Создаем временную папку для хранения частей файла
ВременныйКаталогЧастей = КаталогВременныхФайлов() + "Chunks_" + Формат(ТекущаяДата(), "ДФ=yyyyMMddhhmmss");
СоздатьКаталог(ВременныйКаталогЧастей);
// Размер одной части - 2 МБ (2 * 1024 * 1024 байт)
РазмерЧасти = 2097152;
// Разбиваем файл на части средствами платформы 1С
МассивЧастей = РазделитьФайл(ПутьКФайлу, РазмерЧасти, ВременныйКаталогЧастей);
КоличествоЧастей = МассивЧастей.Количество();
// Передаем части последовательно
Для Индекс = 0 По КоличествоЧастей - 1 Цикл
ТекущийФайлЧасти = МассивЧастей[Индекс];
ДвоичныеДанныеЧасти = Новый ДвоичныеДанные(ТекущийФайлЧасти);
СтрокаBase64 = Base64Строка(ДвоичныеДанныеЧасти);
// Информируем приемник: передаем саму строку, имя файла, номер части и признак окончания
ЭтоПоследняяЧасть = (Индекс = КоличествоЧастей - 1);
COMСоединение.ПринятьЧастьФайла(СтрокаBase64, ИмяФайла, Индекс, ЭтоПоследняяЧасть);
// Освобождаем память
ДвоичныеДанныеЧасти = Неопределено;
СтрокаBase64 = "";
КонецЦикла;
// Очищаем временную папку на источнике
УдалитьФайлы(ВременныйКаталогЧастей);
КонецПроцедуры
2. Сборка частей на стороне приемника:
В базе-приемнике мы сохраняем поступающие части во временный каталог, используя уникальный идентификатор сессии, а при получении признака последней части объединяем их.
// Процедура в общем модуле базы-приемника
Процедура ПринятьЧастьФайла(СтрокаBase64, ИмяФайла, НомерЧасти, ЭтоПоследняяЧасть) Экспорт
// Создаем стабильную папку для накопления частей конкретного файла
КаталогНакопления = КаталогВременныхФайлов() + "Upload_" + ИмяФайла + "\";
Если НомерЧасти = 0 Тогда
// Если это первая часть, гарантированно очищаем старые остатки
УдалитьФайлы(КаталогНакопления);
СоздатьКаталог(КаталогНакопления);
КонецЕсли;
// Записываем полученную часть на диск
// Форматируем имя файла-части так, чтобы сортировка имен соответствовала порядку частей
ИмяФайлаЧасти = КаталогНакопления + ИмяФайла + "." + Формат(НомерЧасти, "ЧЦ=5; ЧДЦ=0; ЧГ=");
ДвоичныеДанные = ИзСтрокиBase64(СтрокаBase64);
ДвоичныеДанные.Записать(ИмяФайлаЧасти);
Если ЭтоПоследняяЧасть Тогда
// Собираем пути ко всем записанным частям
НайденныеЧасти = НайтиФайлы(КаталогНакопления, "*");
МассивПутейКЧастям = Новый Массив;
Для Каждого ФайлЧасти Из НайденныеЧасти Цикл
МассивПутейКЧастям.Добавить(ФайлЧасти.ПолноеИмя);
КонецЦикла;
// Итоговый собранный файл
ИтоговыйФайл = КаталогВременныхФайлов() + ИмяФайла;
// Склеиваем файлы обратно
ОбъединитьФайлы(МассивПутейКЧастям, ИтоговыйФайл);
// Помещаем собранный файл в информационную базу приемника
ЗафиксироватьФайлВСистеме(ИтоговыйФайл, ИмяФайла);
// Полностью очищаем временную рабочую директорию
УдалитьФайлы(КаталогНакопления);
УдалитьФайлы(ИтоговыйФайл);
КонецЕсли;
КонецПроцедуры
Этот подход гарантирует, что сервер 1С никогда не потребит оперативной памяти больше, чем размер одной порции (в нашем примере — 2 МБ), вне зависимости от того, передаете вы файл размером 10 МБ или 10 ГБ. Операция будет работать исключительно стабильно.
Несмотря на то, что передача файлов через COM-соединение технически реализуема, в современных реалиях разработки на платформе 1С:Предприятие от этой технологии рекомендуется отказываться в пользу более гибких веб-технологий. Основные причины:
comcntr.dll).Если вы проектируете новую систему обмена, рассмотрите два лучших альтернативных сценария:
HTTPЗапрос методом POST или PUT. Платформа 1С позволяет передавать тело HTTP-запроса напрямую из файла на диске без загрузки в оперативную память.Использование Base64 и поблочной передачи через COM-соединение остается отличным «быстрым» решением в рамках существующей Windows-инфраструктуры, но переход на веб-сервисы гарантирует долговечность и надежность вашей архитектуры в будущем.