Как реализовать печать шаблонов MS Word с сервера 1С без ошибок передачи данных?

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

Перенос программного кода из клиентских процедур на сторону сервера — стандартная задача при оптимизации и переходе 1С:Предприятия на клиент-серверную архитектуру. Однако разработчики часто сталкиваются с серьезной проблемой: привычные механизмы формирования офисных документов через COM-соединение (например, COMОбъект("Word.Application")) или использование объектов типа Active Document напрямую на сервере либо вызывают критические ошибки выполнения, либо приводят к нестабильной работе всей системы.

В этой статье мы подробно разберем, почему старый «клиентский» подход дает сбои на сервере, как исправить популярную ошибку передачи данных XDTO и рассмотрим три практических метода реализации серверной печати шаблонов MS Word: от классического гибридного сценария до современного кроссплатформенного механизма на базе БСП.

Почему исходный код печати через COM/Active Document не работает на сервере?

Попытка вызвать клиентский код работы с MS Word на стороне сервера 1С (особенно в контексте выполнения фоновых заданий или серверных вызовов управляемых форм) неизбежно приводит к ошибкам. Выделим три ключевые причины:

  1. Ошибка передачи данных XDTO (сериализация): При передаче макета или COM-объекта напрямую между клиентом и сервером платформа пытается сериализовать объект в XML-формат. Поскольку COM-объекты и интерфейсы Active Document жестко привязаны к конкретному адресному пространству операционной системы и не могут быть преобразованы в XML, платформа генерирует исключение: «Ошибка преобразования данных XDTO: Значение недопустимого типа».
  2. Архитектурные ограничения ОС: Технология COM/OLE является проприетарной разработкой Microsoft. Если сервер приложений 1С развернут на операционной системе семейства Linux, создать объект Word.Application на нем физически невозможно.
  3. Проблемы безопасности и зависания (DCOM): Даже если сервер работает под управлением Windows, запуск интерактивных приложений (таких как MS Word или MS Excel) из-под системной службы 1С (обычно выполняющейся от имени пользователя USR1CV83) заблокирован политиками безопасности Windows. Попытки обойти это ограничение через утилиту dcomcnfg часто приводят к тому, что процессы WINWORD.EXE «зависают» в памяти сервера, постепенно перегружая оперативную память и процессор, что в конечном итоге вызывает падение рабочих процессов сервера 1С.

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

---

Решение 1: Гибридный метод (Подготовка данных на Сервере, печать — на Клиенте через COM)

Если в вашей организации все пользователи работают исключительно под управлением Windows и на их локальных компьютерах установлен Microsoft Office, самым быстрым и простым способом адаптации существующего кода является гибридный метод.

При таком подходе сервер 1С отвечает только за выборку данных из базы данных и получение двоичного макета из метаданных конфигурации. Сам процесс генерации файла, запуск MS Word и заполнение полей происходят на стороне клиента. Это исключает нагрузку на сервер и решает проблему зависания процессов WINWORD.EXE.

Разберем по шагам программную реализацию этого метода. Нам понадобятся две процедуры: одна серверная (для получения макета в виде двоичных данных и помещения его во временное хранилище) и одна клиентская (для получения файла из хранилища и заполнения параметров через COM).

Рассмотрим пример кода:


&НаСервере
Функция ПолучитьШаблонИзМакетНаСервере() Экспорт
    // Получаем макет типа "Двоичные данные" (или Active Document) из метаданных.
    // Обратите внимание: мы не открываем документ на сервере, а получаем его как двоичные данные.
    ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
    МакетДвоичныеДанные = ОбработкаОбъект.ПолучитьМакет("ПФ_DOCX_ШаблонПриказа");
    
    // Помещаем двоичные данные во временное хранилище. 
    // Адрес временного хранилища - это простая строка, которая легко передается на клиент.
    Возврат ПоместитьВоВременноеХранилище(МакетДвоичныеДанные, УникальныйИдентификатор);
КонецФункции

&НаКлиенте
Процедура СформироватьИОткрытьДокументWord(Команда)
    // Шаг 1. Получаем адрес файла во временном хранилище
    АдресХранилища = ПолучитьШаблонИзМакетНаСервере();
    
    // Шаг 2. Создаем временный файл на локальном компьютере пользователя
    ПутьКВременномуФайлу = ПолучитьИмяВременногоФайла("docx");
    ДвоичныеДанныеМакета = ПолучитьИзВременногоХранилища(АдресХранилища);
    ДвоичныеДанныеМакета.Записать(ПутьКВременномуФайлу);
    
    // Шаг 3. Инициализируем COM-объект на клиенте
    Попытка
        WordПриложение = Новый COMОбъект("Word.Application");
    Исключение
        ПоказатьПредупреждение(, "Не удалось запустить Microsoft Word. Убедитесь, что приложение установлено.");
        Возврат;
    КонецПопытки;
    
    // Шаг 4. Открываем наш временный файл-шаблон
    WordДокумент = WordПриложение.Documents.Add(ПутьКВременномуФайлу);
    
    // Отключаем видимость на время заполнения для ускорения работы
    WordПриложение.Visible = Ложь;
    
    // Шаг 5. Пример заполнения одиночных параметров (закладок или переменных)
    Попытка
        // Заполнение через механизм переменных документа (Variables)
        WordДокумент.Variables("OrgName").Value = "ООО \"Рога и Копыта\"";
        WordДокумент.Variables("DocumentNumber").Value = "45-к/2023";
        WordДокумент.Variables("DocumentDate").Value = Формат(ТекущаяДата(), "ДЛФ=DD");
        
        // Обновляем поля в документе, чтобы переменные отобразились в тексте
        WordДокумент.Fields.Update();
    Исключение
        Сообщить("Ошибка при заполнении параметров заголовка документа: " + ОписаниеОшибки());
    КонецПопытки;
    
    // Шаг 6. Работа с табличной частью документа Word
    // Предположим, что в шаблоне Word уже создана пустая таблица (минимум 1 строка-шапка и 1 строка-шаблон)
    Если WordДокумент.Tables.Count > 0 Тогда
        WordТаблица = WordДокумент.Tables(1); // Индексация в COM Word начинается с 1
        
        // Получаем данные для таблицы с сервера (например, структуру или таблицу значений)
        ДанныеДляТаблицы = ПолучитьДанныеТабличнойЧастиНаСервере();
        
        ТекущаяСтрокаДокумента = 2; // Предполагаем, что первая строка - это шапка таблицы
        НомерПП = 1;
        
        Для Каждого СтрокаДанных Из ДанныеДляТаблицы Цикл
            // Если это не первая строка данных, добавляем новую строку в таблицу Word
            Если НомерПП > 1 Тогда
                WordТаблица.Rows.Add();
            КонецЕсли;
            
            // Заполняем ячейки таблицы
            WordТаблица.Cell(ТекущаяСтрокаДокумента, 1).Range.Text = Строка(НомерПП);
            WordТаблица.Cell(ТекущаяСтрокаДокумента, 2).Range.Text = СтрокаДанных.СотрудникФИО;
            WordТаблица.Cell(ТекущаяСтрокаДокумента, 3).Range.Text = СтрокаДанных.Должность;
            WordТаблица.Cell(ТекущаяСтрокаДокумента, 4).Range.Text = Формат(СтрокаДанных.Оклад, "ЧДЦ=2");
            
            ТекущаяСтрокаДокумента = ТекущаяСтрокаДокумента + 1;
            НомерПП = НомерПП + 1;
        КонецЦикла;
    КонецЕсли;
    
    // Шаг 7. Показываем готовый документ пользователю
    WordПриложение.Visible = Истина;
    WordПриложение.Activate();
    
    // Очищаем временный файл (необязательно, но рекомендуется для поддержания чистоты диска)
    // Внимание: удалять файл нужно только после закрытия Word пользователем, либо доверить это операционной системе.
КонецПроцедуры

Этот подход полностью решает проблему ошибки передачи данных XDTO, так как COM-объект создается исключительно на стороне клиента, а между сервером и клиентом передаются только бинарные данные шаблона.

---

Решение 2: Кроссплатформенная серверная печать через БСП (Рекомендуемый современный метод)

Если перед вами стоит задача сделать печать полностью серверной, независимой от наличия установленного Microsoft Office на сервере или операционной системы (Windows/Linux), необходимо использовать механизмы Библиотеки Стандартных Подсистем (БСП). При необходимости автоматизировать формирование документов без сложного программирования можно использовать платформу One RPA для формирования документов и роботизации действий пользователя.

БСП предоставляет готовую подсистему «Печать», которая умеет парсить файлы шаблонов форматов .docx (MS Word) и .odt (LibreOffice) прямо на сервере. Эти файлы физически представляют собой ZIP-архивы, содержащие разметку XML. Платформа 1С распаковывает их, выполняет замену параметров и собирает обратно в готовый файл. MS Word на сервере для этого не нужен.

Шаг 1. Подготовка шаблона в формате DOCX

Вам необходимо создать файл в MS Word и разметить его специальными тегами БСП:

Шаг 2. Реализация печати на сервере через программный интерфейс БСП

Проанализируем пример кода формирования печатной формы на сервере с использованием общего модуля БСП УправлениеПечатью:


&НаСервере
Функция СформироватьДокументНаСервереБСП(СсылкаНаОбъект)
    // 1. Получаем двоичные данные макета из метаданных нашей конфигурации
    ДвоичныеДанныеМакета = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьПриказов.ПФ_DOCX_ШаблонПриказа");
    
    // 2. Инициализируем макет офисного документа средствами БСП
    // Метод определяет тип макета (docx или odt) и подготавливает структуру для заполнения
    Макет = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанныеМакета, "DOCX");
    
    // 3. Создаем структуру печатной формы
    ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФормуОфисногоДокумента("DOCX");
    
    // 4. Заполняем Шапку документа
    // Получаем область шапки из макета
    ОбластьШапка = УправлениеПечатью.ОбластьОфисногоДокумента(Макет, "Область.Шапка");
    
    // Создаем структуру с данными для заполнения параметров шапки
    ДанныеПараметровШапки = Новый Структура;
    ДанныеПараметровШапки.Вставить("Организация", СсылкаНаОбъект.Организация.Наименование);
    ДанныеПараметровШапки.Вставить("НомерПриказа", СсылкаНаОбъект.Номер);
    ДанныеПараметровШапки.Вставить("ДатаПриказа", Формат(СсылкаНаОбъект.Дата, "ДЛФ=DD"));
    
    // Присоединяем заполненную область к нашей печатной форме
    УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьШапка, ДанныеПараметровШапки);
    
    // 5. Заполняем табличную часть (Область строки таблицы)
    ОбластьСтроки = УправлениеПечатью.ОбластьОфисногоДокумента(Макет, "Область.СтрокаТаблицы");
    
    НомерСтроки = 1;
    Для Каждого ТекСтрока Из СсылкаНаОбъект.Сотрудники Цикл
        ДанныеПараметровСтроки = Новый Структура;
        ДанныеПараметровСтроки.Вставить("НомерПП", НомерСтроки);
        ДанныеПараметровСтроки.Вставить("ФИО", ТекСтрока.Сотрудник.Наименование);
        ДанныеПараметровСтроки.Вставить("Должность", ТекСтрока.Должность.Наименование);
        ДанныеПараметровСтроки.Вставить("Оклад", Формат(ТекСтрока.Оклад, "ЧДЦ=2; ЧГ=0"));
        
        // Присоединяем каждую заполненную строку к общей таблице в документе
        УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьСтроки, ДанныеПараметровСтроки);
        НомерСтроки = НомерСтроки + 1;
    КонецЦикла;
    
    // 6. Заполняем Подвал документа
    ОбластьПодвал = УправлениеПечатью.ОбластьОфисногоДокумента(Макет, "Область.Подвал");
    ДанныеПодвала = Новый Структура;
    ДанныеПодвала.Вставить("Руководитель", СсылкаНаОбъект.Организация.Руководитель.Наименование);
    
    УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьПодвал, ДанныеПодвала);
    
    // 7. Собираем итоговые двоичные данные заполненного документа DOCX
    ДанныеГотовогоДокумента = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);
    
    // Помещаем готовый файл во временное хранилище, чтобы передать на клиент
    Возврат ПоместитьВоВременноеХранилище(ДанныеГотовогоДокумента, УникальныйИдентификатор);
КонецФункции

Для открытия сформированного документа на клиенте используется простой код:


&НаКлиенте
Процедура НапечататьДокументБСП(Команда)
    // Получаем заполненный DOCX-файл с сервера
    АдресХранилища = СформироватьДокументНаСервереБСП(Объект.Ссылка);
    
    // Сохраняем его во временную директорию клиента и запускаем ассоциированное приложение
    ПутьКФайлу = ПолучитьИмяВременногоФайла("docx");
    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
    ДвоичныеДанные.Записать(ПутьКФайлу);
    
    // Открываем файл в локальном текстовом редакторе пользователя (MS Word, LibreOffice, OnlyOffice)
    ЗапуститьПриложение(ПутьКФайлу);
КонецПроцедуры

Данный подход является наиболее отказоустойчивым, современным и безопасным для серверной архитектуры 1С.

---

Решение 3: Прямая XML-инъекция (Для самописных конфигураций без БСП)

Рассмотрим ситуацию, когда ваша конфигурация полностью самописная, подсистемы БСП в ней отсутствуют, а ставить тяжелую библиотеку только ради одной печатной формы нецелесообразно. В этом случае мы можем использовать метод прямого редактирования XML-структуры DOCX-файла.

Как мы уже выяснили, файл .docx — это ZIP-архив. Основное текстовое содержимое документа хранится внутри архива по пути word/document.xml. Алгоритм действий на сервере выглядит следующим образом:

  1. Извлекаем макет (двоичные данные шаблона DOCX) во временную папку на сервере.
  2. Распаковываем полученный файл с помощью стандартного объекта 1С ЧтениеZipФайла.
  3. Считываем содержимое текстового файла word/document.xml в переменную.
  4. Используя функцию СтрЗаменить(), заменяем заранее заготовленные текстовые маркеры (например, [НомерПриказа], [Организация]) на актуальные данные из 1С.
  5. Записываем измененную строку обратно в файл word/document.xml.
  6. Упаковываем файлы обратно в ZIP-архив с расширением .docx с помощью объекта 1С ЗаписьZipФайла.
  7. Передаем готовый архив на клиент через временное хранилище.

Рассмотрим схематичный пример реализации данного алгоритма:


&НаСервере
Функция СформироватьDocxМетодомЗаменыНаСервере(СсылкаНаОбъект)
    // Получаем макет двоичных данных шаблона
    ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
    ДвоичныеДанныеШаблона = ОбработкаОбъект.ПолучитьМакет("ПФ_DOCX_Шаблон");
    
    // Определяем пути для работы с файлами на сервере
    КаталогРаспаковки = КаталогВременныхФайлов() + "docx_unpack_" + НоваяУникальнаяСтрока() + "\";
    ИмяФайлаАрхива = КаталогВременныхФайлов() + "temp_shablon.zip";
    
    // Записываем макет как ZIP-архив
    ДвоичныеДанныеШаблона.Записать(ИмяФайлаАрхива);
    
    // Распаковываем архив
    Архив = Новый ЧтениеZipФайла(ИмяФайлаАрхива);
    Архив.ИзвлечьВсе(КаталогРаспаковки, РежимВосстановленияПутейФайловZIP.Восстанавливать);
    Архив.Закрыть();
    
    // Путь к основному XML-файлу с текстом документа
    ПутьКXML = КаталогРаспаковки + "word/document.xml";
    
    // Читаем содержимое XML в строку
    ТекстовыйДокумент = Новый ТекстовыйДокумент;
    ТекстовыйДокумент.Прочитать(ПутьКXML, КодировкаТекста.UTF8);
    ТекстXML = ТекстовыйДокумент.ПолучитьТекст();
    
    // Выполняем замены параметров
    ТекстXML = СтрЗаменить(ТекстXML, "[Организация]", СсылкаНаОбъект.Организация.Наименование);
    ТекстXML = СтрЗаменить(ТекстXML, "[Номер]", СсылкаНаОбъект.Номер);
    ТекстXML = СтрЗаменить(ТекстXML, "[Дата]", Формат(СсылкаНаОбъект.Дата, "ДЛФ=D"));
    
    // Сохраняем измененный XML
    ТекстовыйДокумент.УстановитьТекст(ТекстXML);
    ТекстовыйДокумент.Записать(ПутьКXML, КодировкаТекста.UTF8);
    
    // Запаковываем обратно в DOCX
    ИмяГотовогоФайла = КаталогВременныхФайлов() + "ready_document.docx";
    НовыйАрхив = Новый ЗаписьZipФайла(ИмяГотовогоФайла);
    НовыйАрхив.Добавить(КаталогРаспаковки + "*", РежимСохраненияПутейZIP.СохранятьОтносительныеПути, РежимОбработкиПодкаталоговZIP.Рекурсивно);
    НовыйАрхив.Записать();
    
    // Читаем готовый файл в двоичные данные для отправки на клиент
    ГотовыеДвоичныеДанные = Новый ДвоичныеДанные(ИмяГотовогоФайла);
    
    // Очищаем временные файлы на сервере
    УдалитьФайлы(КаталогРаспаковки);
    УдалитьФайлы(ИмяФайлаАрхива);
    УдалитьФайлы(ИмяГотовогоФайла);
    
    Возврат ПоместитьВоВременноеХранилище(ГотовыеДвоичныеДанные, УникальныйИдентификатор);
КонецФункции

Этот метод работает чрезвычайно быстро, не требует установленного офисного ПО ни на клиенте, ни на сервере, однако имеет один важный минус: в нем достаточно сложно реализовывать динамическое добавление строк в таблицы, так как для этого придется программно генерировать сложные XML-блоки тегов <w:tr> и <w:tc>.

---

Сравнительный анализ решений: какое выбрать?

Для того чтобы облегчить выбор архитектурного решения, проанализируем преимущества и недостатки каждого метода:

Критерий сравнения Метод 1: Гибридный (Клиентский COM) Метод 2: БСП (Серверный DOCX) Метод 3: XML-инъекция (Сервер)
Где выполняется формирование На Клиенте На Сервере На Сервере
Требования к ПО на Сервере 1С Отсутствуют Отсутствуют Отсутствуют
Требования к ПО на Клиенте Обязательно наличие MS Word Любой просмотрщик DOCX (или без него) Любой просмотрщик DOCX (или без него)
Поддерживаемые ОС на сервере Любая (Windows, Linux) Любая (Windows, Linux) Любая (Windows, Linux)
Сложность вывода таблиц Низкая (простой обход циклом) Низкая (через области БСП) Высокая (ручная сборка XML-тегов)
Скорость работы Низкая (COM-вызовы медленные) Высокая Максимальная

Подведем итоги: Если ваша информационная база построена на базе типовой конфигурации (ЕРП, КА, УТ, БП, ЗУП), лучшим выбором будет использование Метода №2 (БСП). Это гарантирует надежность, быструю работу на сервере и простоту поддержки шаблонов обычными пользователями. К гибридному Методу №1 стоит прибегать только в случае поддержки старых легаси-систем, когда переделка большого объема логики нецелесообразна по бюджету.

← На главную