При разработке механизмов интеграции и выгрузки данных из «1С:Предприятие» во внешние офисные пакеты программисты часто используют COM-соединение с приложением Microsoft Excel. Это мощный инструмент, который, однако, таит в себе множество подводных камней, связанных с различием версий программного обеспечения и его настроек на конечных рабочих станциях. Одной из типичных и весьма коварных ошибок является сбой с кодом 0x8002000b на строке обращения к листам книги, например, при вызове КнигаExcel.WorkSheets.Item(2). Давайте подробно проанализируем ситуацию, выясним истинную причину этого поведения и разберем по шагам, как написать отказоустойчивый код.
Для начала рассмотрим фрагмент кода, который приводит к аварийному завершению работы программы:
Excel = Новый COMОбъект("Excel.Application");
Excel.Workbooks.Open(КаталогВременныхФайлов() + "tmpxls.xls");
tmpBook = Excel.Workbooks.Item(Excel.Workbooks.Count);
КнигаExcel = Excel.Workbooks.Add();
ЛистТаблицы = КнигаExcel.WorkSheets.Item(1);
ЛистТаблицы.Name = "Сводная таблица";
ЛистДанных = КнигаExcel.WorkSheets.Item(2); // Ошибка возникает здесь!
ЛистДанных.Name = "Данные";
С точки зрения COM-интерфейсов операционной системы Windows, шестнадцатеричный код ошибки 0x8002000b расшифровывается как системное событие DISP_E_BADINDEX (недопустимый индекс). Это означает, что программа пытается обратиться к элементу коллекции по порядковому номеру, которого физически не существует в данной коллекции на момент вызова. В нашем примере 1С пытается получить второй лист (индекс 2) новой рабочей книги Excel, но коллекция WorkSheets пуста или содержит меньшее количество элементов.
Заявление пользователей о том, что еще вчера отчет формировался корректно, очень часто сбивает разработчиков с толку. На самом деле, поведение метода Workbooks.Add() напрямую зависит от внешних факторов окружения, которые могли измениться:
Add() автоматически создавалось 3 листа (Лист1, Лист2, Лист3). Поэтому старый программный код успешно обращался ко второму и третьему листу без генерации исключений. Начиная с версии Excel 2013, а также в Excel 2016, 2019, 2021 и Office 365, компания Microsoft оптимизировала потребление ресурсов и изменила логику по умолчанию: теперь новая книга создается только с одним листом.Полагаться на локальные настройки стороннего софта в промышленном коде крайне опасно. Программа 1С должна самостоятельно управлять структурой создаваемого файла Excel. Рассмотрим подробнее способы решения данной проблемы. В ситуациях, когда требуется не выгрузить, а загрузить данные из внешних файлов, применяется загрузчик отчетов маркетплейсов из Excel в 1С:Бухгалтерию, который корректно обрабатывает файлы Ozon, Wildberries и других площадок.
Самым надежным способом устранения ошибки является явное добавление необходимого количества листов через вызов метода Add() коллекции Worksheets. Таким образом, мы не пытаемся угадать настройки системы, а конструируем книгу «с нуля» самостоятельно. Посмотрим на пример практической реализации:
Excel = Новый COMОбъект("Excel.Application");
КнигаExcel = Excel.Workbooks.Add();
// Первый лист гарантированно существует в любой версии Excel
ЛистТаблицы = КнигаExcel.WorkSheets.Item(1);
ЛистТаблицы.Name = "Сводная таблица";
// Не ищем второй лист, а принудительно создаем новый.
// Метод Add() возвращает ссылку на вновь созданный лист.
ЛистДанных = КнигаExcel.WorkSheets.Add();
ЛистДанных.Name = "Данные";
При использовании метода Add() новый лист добавляется в книгу, и вы сразу получаете прямую ссылку на него в переменную ЛистДанных. Это полностью исключает возникновение ошибки DISP_E_BADINDEX.
В случаях, когда структура книги формируется динамически на основе входящего массива данных, и заранее неизвестно конечное количество листов, рекомендуется использовать циклическую проверку количества элементов в коллекции. Проанализируем пример такого кода:
КнигаExcel = Excel.Workbooks.Add();
НеобходимоеКоличествоЛистов = 2; // Например, нам нужно заполнить ровно два листа
// Добавляем листы в цикле, пока их количество не удовлетворит условию
Пока КнигаExcel.WorkSheets.Count < НеобходимоеКоличествоЛистов Цикл
КнигаExcel.WorkSheets.Add();
КонецЦикла;
// Теперь мы можем абсолютно безопасно обращаться к индексам по порядку
ЛистТаблицы = КнигаExcel.WorkSheets.Item(1);
ЛистТаблицы.Name = "Сводная таблица";
ЛистДанных = КнигаExcel.WorkSheets.Item(2);
ЛистДанных.Name = "Данные";
Этот способ универсален, так как он корректно сработает и в том случае, если на одном компьютере Excel настроен на создание 1 листа, а на другом — на создание 3 или 5 листов. Лишние листы (если их создалось больше нужного) при необходимости можно удалить методом Delete().
Объектная модель Excel позволяет разработчику временно переопределить глобальные настройки приложения для текущей запущенной сессии. Свойство SheetsInNewWorkbook указывает, сколько листов должно автоматически создаваться во всех последующих вызовах метода Add():
Excel = Новый COMОбъект("Excel.Application");
// Принудительно задаем создание ровно двух листов в рамках текущего сеанса COM
Excel.SheetsInNewWorkbook = 2;
КнигаExcel = Excel.Workbooks.Add(); // Книга создается сразу с двумя листами
ЛистТаблицы = КнигаExcel.WorkSheets.Item(1);
ЛистТаблицы.Name = "Сводная таблица";
ЛистДанных = КнигаExcel.WorkSheets.Item(2);
ЛистДанных.Name = "Данные";
Данная настройка является «чистой», так как она применяется только к программно запущенному процессу excel.exe и никак не влияет на параметры Excel при его обычном интерактивном запуске пользователем.
При написании процедур интеграции с внешними приложениями важно не только правильно обращаться к их методам, но и корректно освобождать системные ресурсы. Внезапные ошибки в коде 1С могут приводить к зависанию фоновых процессов Excel в памяти сервера или клиентского ПК. Чтобы этого избежать, всегда оборачивайте логику работы с внешними файлами в защитный блок транзакции:
Excel = Неопределено;
Попытка
Excel = Новый COMОбъект("Excel.Application");
// Основной алгоритм работы с файлом
// ...
Исключение
ЗаписьЖурналаРегистрации("Выгрузка в Excel", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Не удалось сформировать отчет. Обратитесь к администратору.";
КонецПопытки;
// Обязательное закрытие приложения в блоке гарантированного завершения
Если Excel <> Неопределено Тогда
Excel.Quit();
Excel = Неопределено;
КонецЕсли;
Инициализация переменной значением Неопределено после вызова метода Quit() сигнализирует операционной системе о необходимости уничтожения COM-соединения и выгрузки процесса excel.exe из оперативной памяти.
Использование COM-соединения имеет ряд критических ограничений: это медленная скорость передачи данных, обязательное требование наличия установленного пакета MS Office на компьютере, где выполняется код, и несовместимость с серверами под управлением ОС Linux. Тем не менее, для контроля результатов обмена и поиска расхождений в проводках используется инструмент автоматической сверки данных через COM-соединение, упрощающий поиск проблемных участков.
Если перед вами стоит задача сформировать качественный многостраничный отчет, рассмотрите современные альтернативные технологии:
Записать() с указанием формата XLSX. Это решение работает на любой операционной системе (включая Linux на сервере) без установки сторонних программ:
ТабДок = Новый ТабличныйДокумент;
// Заполняем табличный документ макетом и данными
// ...
ТабДок.Записать("C:\temp\SvodnyReport.xlsx", ТипФайлаТабличногоДокумента.XLSX);