При разработке отчетов и обработок в системе 1С:Предприятие часто возникает необходимость выгрузить сложный, заранее подготовленный файл-шаблон Excel, заполнить его данными из информационной базы и открыть пользователю на клиенте. Традиционно для хранения таких шаблонов разработчики используют макеты типа ActiveDocument.
Однако в управляемом приложении (клиент-серверный режим) эта простая задача превращается в настоящий квест. Разработчики сталкиваются с тем, что тип макета ActiveDocument (на сервере представляющий собой объект ОболочкаActiveDocument) не может быть напрямую передан на клиент из-за ограничений сериализации. Попытка передать файл через временное хранилище и открыть его с помощью COM-объекта на стороне клиента часто приводит к труднодиагностируемым системным ошибкам, таким как легендарная ошибка Windows DCOM 0x80080005 (Ошибка при выполнении приложения-сервера).
В этой статье мы подробно разберем, почему возникают эти проблемы, как правильно организовать транспорт файла-макета с сервера на клиент и как раз и навсегда победить сбои при инициализации COM-соединения с Excel.
---
Прежде чем писать код, давайте разберем системные причины возникновения этой ошибки. Код ошибки 0x80080005 возвращается операционной системой Windows (механизмом DCOM) и сигнализирует о том, что системе не удалось запустить или подключиться к зарегистрированному серверу автоматизации (в нашем случае — процессу excel.exe). Основные причины сбоя на клиенте:
excel.exe. Новые попытки создать COM-объект блокируются операционной системой по таймауту.ПолучитьCOMОбъект(). Этот метод 1С использует системные моникеры Windows. Он пытается по расширению файла определить нужное приложение и запустить его. Любая ошибка в ассоциациях файлов в реестре Windows полностью ломает работу этого метода.---
Рассмотрим правильную архитектуру передачи файла. Напрямую вернуть ActiveDocument на клиент нельзя. Поэтому мы должны выгрузить макет во временный файл на сервере, преобразовать его в поток байтов (объект ДвоичныеДанные), поместить во временное хранилище, передать адрес на клиент, а там воссоздать файл на локальном диске.
Давайте проанализируем пример кода серверной функции:
&НаСервере
Функция ПолучитьАдресМакетаВХранилище()
// Получаем объект обработки или отчета из реквизита формы
ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
// Получаем макет ActiveDocument (на сервере это ОболочкаActiveDocument)
Макет = ОбработкаОбъект.ПолучитьМакет("МакетExcel");
// Создаем имя временного файла на СЕРВЕРЕ.
// Обратите внимание на обязательную точку перед расширением!
ИмяВременногоФайлаСервер = ПолучитьИмяВременногоФайла(".xls");
// Записываем макет во временный файл на диске сервера
Макет.Записать(ИмяВременногоФайлаСервер);
// Преобразуем файл в двоичные данные и помещаем во временное хранилище
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяВременногоФайлаСервер);
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеФайла, УникальныйИдентификатор);
// Удаляем временный файл на сервере, он больше не нужен
Попытка
УдалитьФайлы(ИмяВременногоФайлаСервер);
Исключение
// Игнорируем ошибку удаления, если файл занят
КонецПопытки;
Возврат АдресХранилища;
КонецФункции
Важная деталь: метод ПолучитьИмяВременногоФайла(".xls") обязательно должен вызываться с точкой в параметре расширения. Если передать просто "xls", платформа может сформировать некорректный путь, из-за чего Windows не сможет правильно ассоциировать файл.
---
Теперь разберем клиентскую часть. Вместо нестабильного метода ПолучитьCOMОбъект(ПутьКФайлу) мы применим гораздо более надежную схему явного создания COM-сервера (Late Binding) с помощью конструктора Новый COMОбъект("Excel.Application"). Это позволит нам полностью контролировать процесс запуска, отображения и закрытия приложения Excel.
Посмотрим на пример клиентской процедуры:
&НаКлиенте
Процедура ВыгрузитьИЗаполнитьМакет(Команда)
АдресХранилища = ПолучитьАдресМакетаВХранилище();
Если НЕ ЗначениеЗаполнено(АдресХранилища) Тогда
ВызватьИсключение "Не удалось получить макет с сервера.";
КонецЕсли;
// Получаем двоичные данные из временного хранилища на клиенте
ДвоичныеДанныеМакета = ПолучитьИзВременногоХранилища(АдресХранилища);
// Генерируем имя временного файла на КЛИЕНТЕ
ИмяВременногоФайлаКлиент = ПолучитьИмяВременногоФайла(".xls");
// Записываем двоичные данные на локальный диск клиента
ДвоичныеДанныеМакета.Записать(ИмяВременногоФайлаКлиент);
// Инициализируем COM-соединение с Excel с обработкой возможных ошибок
ExcelApp = Неопределено;
Попытка
ExcelApp = Новый COMОбъект("Excel.Application");
Исключение
Сообщить("Не удалось запустить Microsoft Excel. Возможно, программа не установлена на компьютере.", СтатусСообщения.Важное);
Сообщить(ОписаниеОшибки());
Возврат;
КонецПопытки;
Попытка
// Скрываем окно Excel на время заполнения данных для ускорения работы
ExcelApp.Visible = Ложь;
ExcelApp.DisplayAlerts = Ложь; // Отключаем системные предупреждения Excel
// Открываем записанный на диск временный файл-шаблон
Книга = ExcelApp.Workbooks.Open(ИмяВременногоФайлаКлиент);
Лист = Книга.Sheets(1);
// Пример заполнения ячеек шаблона
Лист.Cells(1, 1).Value = "Отчет по продажам";
Лист.Cells(3, 2).Value = ТекущаяДата();
// ... Здесь вы можете написать свой алгоритм заполнения данных ...
// Сохраняем изменения в файле
Книга.Save();
// Делаем Excel видимым для пользователя
ExcelApp.Visible = Истина;
Исключение
Сообщить("Произошла ошибка при заполнении файла Excel!");
Сообщить(ОписаниеОшибки());
// Обязательно закрываем процесс Excel в случае аварии
Если ExcelApp <> Неопределено Тогда
ExcelApp.Quit();
ExcelApp = Неопределено;
КонецЕсли;
КонецПопытки;
КонецПроцедуры
Использование конструкции ExcelApp.Quit() в блоке обработки исключений гарантирует, что в вашей системе не останется «зависших» процессов Excel, которые в дальнейшем могли бы вызвать ошибку 0x80080005.
---
Если вы применили надежную схему создания COM-объекта через Excel.Application, но ошибка все равно периодически появляется, выполните следующие системные действия на клиентском компьютере:
excel.exe.Desktop. Создайте пустые каталоги по следующим путям (в зависимости от разрядности ОС):
C:\Windows\SysWOW64\config\systemprofile\DesktopC:\Windows\System32\config\systemprofile\Desktop---
Использование технологии COM накладывает жесткие ограничения на архитектуру вашей системы: код будет работать исключительно под операционной системой Windows, а на каждом клиентском компьютере должен быть установлен дорогостоящий лицензионный пакет Microsoft Office. В современных реалиях (веб-клиент, работа под Linux, macOS или мобильные платформы) использование COM-объектов считается устаревшей практикой.
Давайте рассмотрим, как решить задачу заполнения Excel-файлов нативными средствами платформы 1С:Предприятие (начиная с версии 8.3.6), используя объект ТабличныйДокумент.
Самый простой и эффективный способ — перенести ваш шаблон Excel в макет 1С с типом Табличный документ. Вы сможете редактировать его прямо в конфигураторе 1С, настраивать шрифты, объединять ячейки и прописывать параметры. Заполнение такого макета происходит мгновенно на стороне сервера и не требует никаких сторонних программ:
&НаСервере
Функция СформироватьОтчетБезCOM()
ОбработкаОбъект = РеквизитФормыВ Значение("Объект");
// Получаем макет типа Табличный документ
Макет = ОбработкаОбъект.ПолучитьМакет("НашМакетТабДок");
ТабДок = Новый ТабличныйДокумент;
// Заполняем области табличного документа данными
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьШапка.Параметры.Заголовок = "Отчет по продажам";
ТабДок.Вывести(ОбластьШапка);
// Записываем полученный результат напрямую в формат XLSX на сервере
ИмяФайла = ПолучитьИмяВременногоФайла(".xlsx");
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
// Помещаем готовый файл в хранилище для передачи клиенту
Возврат ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ИмяФайла));
КонецФункции
Если вам обязательно нужно работать со сторонним готовым файлом Excel (например, загруженным пользователем), вы можете прочитать его структуру, изменить нужные ячейки прямо в коде 1С и записать обратно, полностью избегая вызовов COM:
&НаСервере
Процедура ИзменитьФайлНативно(АдресФайлаВХранилище)
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресФайлаВХранилище);
ИмяФайла = ПолучитьИмяВременногоФайла(".xlsx");
ДвоичныеДанныеФайла.Записать(ИмяФайла);
// Инициализируем табличный документ 1С
ТабДок = Новый ТабличныйДокумент;
// Платформа 1С самостоятельно парсит структуру Excel без запуска Excel.Application
ТабДок.Прочитать(ИмяФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
// Обращаемся к ячейкам по правилам 1С (строка, колонка)
ТабДок.Область("R1C1").Текст = "Измененный на сервере заголовок";
ТабДок.Область("R5C2").Значение = 15500;
// Записываем изменения обратно в файл
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
КонецПроцедуры
Резюме: Передача макета ActiveDocument с сервера на клиент должна осуществляться исключительно через преобразование файла в двоичные данные и временное хранилище. При необходимости использования COM-объектов на клиенте следует отказаться от ненадежного ПолучитьCOMОбъект() в пользу явного создания экземпляра Excel.Application с обязательной обработкой исключений и закрытием процесса. Если же перед вами стоит задача кроссплатформенной разработки — смело переводите ваши макеты в нативный формат ТабличныйДокумент 1С.