Как передать файлы через COM-соединение в 1С:Предприятие?

Программист 1С v8.3 (Управляемые формы) 1С:Документооборот IT и автоматизация бизнеса
← На главную

При интеграции различных конфигураций 1С часто возникает необходимость автоматизировать перенос документов вместе с прикрепленными к ним файлами. Если перенос реквизитов (строк, чисел, дат) через COM-соединение не вызывает трудностей, то передача бинарных данных (сканов договоров, PDF-файлов, изображений) напрямую невозможна. Это связано с тем, что сложные объекты платформы, такие как ДвоичныеДанные, не могут быть переданы через COM-границу «по значению» — они передаются только в виде ссылок (COM-объектов), которые мгновенно теряют работоспособность при закрытии соединения.

Для успешного решения этой задачи файл необходимо преобразовать в примитивный тип (строку) на стороне базы-источника, передать эту строку как параметр метода COM-объекта, а затем восстановить файл на стороне базы-приемника. В этой статье мы подробно разберем несколько проверенных способов реализации этого механизма, их плюсы, минусы и особенности оптимизации.

Способ 1. Использование кодирования в Base64 (Рекомендуемый метод)

Преобразование файла в строку формата Base64 — это самый простой, быстрый и стабильный способ передачи бинарных данных через COM-соединение. Платформа 1С:Предприятие имеет высокопроизводительные встроенные функции для работы с этим форматом, которые работают на уровне платформы без привлечения дополнительных сериализаторов.

Рассмотрим по шагам, как реализовать этот алгоритм.

Шаг 1. Кодирование на стороне источника

В базе-источнике мы считываем файл с диска в объект ДвоичныеДанные, преобразуем его в Base64-строку и передаем в метод COM-соединения:


// Код выполняется в базе-источнике
ПутьКФайлу = "C:\Files\Contract_123.pdf";

Попытка
    ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ПутьКФайлу);
    // Преобразуем двоичные данные в Base64-строку
    СтрокаПередачи = Base64Строка(ДвоичныеДанныеФайла);
Исключение
    ЗаписатьЛогСобытий("Ошибка чтения файла: " + ОписаниеОшибки());
    Возврат;
КонецПопытки;

// Вызываем экспортный метод в базе-приемнике через COM
ИмяФайла = "Договор_123.pdf";
КомСоединение.ПринятьФайлИзИсточника(СтрокаПередачи, ИмяФайла);

Шаг 2. Декодирование на стороне приемника

В общем модуле базы-приемника (который должен иметь флаг «Вызов сервера» или запускаться во внешнем соединении) описываем экспортную процедуру восстановления файла:


// Код выполняется в базе-приемнике (например, 1С:Документооборот)
Процедура ПринятьФайлИзИсточника(СтрокаBase64, ИмяФайла) Экспорт

    Попытка
        // Восстанавливаем двоичные данные из полученной строки
        ДвоичныеДанные = ИзСтрокиBase64(СтрокаBase64);
        
        // Определяем временный путь для сохранения файла на сервере приемника
        ИмяВременногоФайла = ПолучитьИмяВременногоФайла(ПолучитьРасширениеФайла(ИмяФайла));
        ДвоичныеДанные.Записать(ИмяВременногоФайла);
        
        // Здесь выполняем логику прикрепления файла к документу в базе-приемнике
        // Например, создаем элемент справочника "Файлы" или "ВерсииФайлов"
        ДобавитьФайлКДокументу(ИмяВременногоФайла, ИмяФайла);
        
        // Удаляем временный файл после прикрепления
        УдалитьФайлы(ИмяВременногоФайла);
    Исключение
        ЗаписатьЛогСобытий("Ошибка сохранения файла в приемнике: " + ОписаниеОшибки());
        ВызватьИсключение;
    КонецПопытки;

КонецПроцедуры

Преимущества метода Base64:

Способ 2. Сериализация XML через ХранилищеЗначения

Этот метод базируется на стандартных механизмах XML-сериализации платформы. Мы упаковываем двоичные данные в ХранилищеЗначения (что дополнительно сжимает файл), а затем преобразуем хранилище в XML-строку с помощью функции XMLСтрока.

Проанализируем реализацию данного подхода:

Код на стороне базы-источника:


// Читаем файл в двоичные данные
ДвоичныеДанные = Новый ДвоичныеДанные(ПутьКФайлу);

// Помещаем в ХранилищеЗначения со сжатием максимальной степени (9)
Хранилище = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));

// Сериализуем хранилище в XML-строку
СтрокаXML = XMLСтрока(Хранилище);

// Передаем полученную строку через COM-соединение
КомОбъект.ПринятьФайлЧерезХранилище(СтрокаXML, "Договор.pdf");

Код на стороне базы-приемника:


Процедура ПринятьФайлЧерезХранилище(СтрокаXML, ИмяФайла) Экспорт
    
    // Восстанавливаем ХранилищеЗначения из XML-строки
    Хранилище = XMLЗначение(Тип("ХранилищеЗначения"), СтрокаXML);
    
    // Извлекаем двоичные данные
    ДвоичныеДанные = Хранилище.Получить();
    
    Если ДвоичныеДанные <> Неопределено Тогда
        ВременныйПуть = КаталогВременныхФайлов() + ИмяФайла;
        ДвоичныеДанные.Записать(ВременныйПуть);
        // Выполняем дальнейшую обработку файла...
    КонецЕсли;
    
КонецПроцедуры

Важное преимущество: Сжатие данных происходит «на лету» средствами платформы, что может существенно уменьшить объем передаваемой строки для текстовых файлов или несжатых форматов документов.

Способ 3. Использование Сериализатора XDTO

Сериализатор 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);
    ДвоичныеДанные.Записать(ПутьСохранения);
    
КонецФункции

Критическая проблема: Переполнение памяти (Ошибка "Bad Allocation")

Все вышеописанные методы обладают одной фундаментальной уязвимостью. При сериализации файла в строку (неважно — 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-соединений

Несмотря на то, что передача файлов через COM-соединение технически реализуема, в современных реалиях разработки на платформе 1С:Предприятие от этой технологии рекомендуется отказываться в пользу более гибких веб-технологий. Основные причины:

  1. Кроссплатформенность: COM (Component Object Model) — это проприетарная технология корпорации Microsoft. Если сервер базы-источника или базы-приемника будет переведен на операционную систему Linux (что сейчас является массовым стандартом), COM-соединение полностью перестанет функционировать.
  2. Проблемы разрядности: При переходе серверов 1С на 64-битные версии часто возникают проблемы с регистрацией и вызовом 32-битных COM-компонентов (ошибки класса comcntr.dll).
  3. Производительность: Использование HTTP-сервисов позволяет передавать файлы напрямую в бинарном потоке без затрат на сериализацию в Base64.

Если вы проектируете новую систему обмена, рассмотрите два лучших альтернативных сценария:

Использование Base64 и поблочной передачи через COM-соединение остается отличным «быстрым» решением в рамках существующей Windows-инфраструктуры, но переход на веб-сервисы гарантирует долговечность и надежность вашей архитектуры в будущем.

← На главную