При разработке обработок заполнения документов из внешних файлов Microsoft Excel программисты часто сталкиваются с аппаратными и системными ошибками COM-соединения. Одной из самых коварных является ошибка с кодом 0x800a03ec. Она сопровождается сообщением «Произошла исключительная ситуация (0x800a03ec)» при вызове метода Cells или Range. В этой статье мы подробно разберем, почему возникает эта ошибка, как исправить исходный код и какие альтернативные, более современные методы импорта данных из Excel существуют в платформе 1С:Предприятие.
Исключительная ситуация с кодом 0x800a03ec — это внутренняя ошибка приложения Microsoft Excel, которая транслируется в 1С через механизм COM (Component Object Model). Проанализируем основные ситуации, при которых Excel возвращает этот код сбоя:
Sheet.Cells(НомерСтроки, 0) или Sheet.Cells(0, НомерКолонки) гарантированно приведет к аварийному завершению работы скрипта с ошибкой 0x800a03ec..xls) поддерживает максимум 65 536 строк и 256 колонок. Если ваш код попытается обратиться к ячейке за этими рамками (например, к 100 000-й строке), система выбросит исключение. Новый формат .xlsx расширяет эти лимиты до 1 048 576 строк и 16 384 колонок, однако некорректно сохраненный файл может выдавать ту же ошибку при попытке прочитать «пустые» области.excel.exe (например, из-за некорректного закрытия COM-соединений в прошлых сессиях 1С), попытка открыть новый сеанс и прочитать ячейки может завершиться сбоем обращения к памяти.Рассмотрим подробнее фрагмент кода, который вызвал проблему у автора темы на форуме. В коде заполнения параметров колонок были допущены критические ошибки инициализации:
// Ошибочный фрагмент инициализации
НомерСтроки = 24;
СтолбецИтогов = 0; // Ошибка! Колонки 0 не существует в Excel
СтолбецШтрихКода = 13;
СтолбецГТД = 0; // Тоже потенциальный источник ошибки при чтении
СтолбецСтрана = 0;
СтолбецВес = 33;
Когда цикл обхода строк начинает работу, он вызывает функцию получения значения ячейки:
Пока Эксель(НомерСтроки,СтолбецИтогов) <> "" Цикл
// Код обработки данных
КонецЦикла;
Функция Эксель(Стр, Столб) обращается напрямую к объекту Excel:
Функция Эксель(Стр,Столб)
Значение = СокрЛП(Sheet.Cells(Стр,Столб).Value);
Возврат Значение;
КонецФункции
Поскольку переменная СтолбецИтогов была равна 0, вызов Sheet.Cells(24, 0) сразу приводил к падению программы с ошибкой 0x800a03ec. Чтобы исправить эту ситуацию, нам необходимо переопределить переменные столбцов так, чтобы они соответствовали реальной структуре загружаемого файла Excel (где первая колонка "A" имеет индекс 1, "B" — 2 и так далее). Для неиспользуемых колонок следует внедрить безопасную проверку:
// Исправленный и безопасный вариант функции чтения ячейки
Функция ЭксельБезопасно(Стр, Столб)
// Если номер колонки или строки некорректен, не обращаемся к COM-объекту
Если Столб <= 0 Или Стр <= 0 Тогда
Возврат "";
КонецЕсли;
Попытка
ЗначениеЯчейки = Sheet.Cells(Стр, Столб).Value;
Возврат СокрЛП(Строка(ЗначениеЯчейки));
Исключение
// Логируем ошибку, но не прерываем выполнение процесса
ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Предупреждение, , ,
"Ошибка чтения ячейки (" + Стр + "," + Столб + "): " + ОписаниеОшибки());
Возврат "";
КонецПопытки;
КонецФункции
Проанализируем ситуацию, когда Excel аварийно закрывается или файл поврежден. Нельзя допускать, чтобы логика программы продолжала выполняться после того, как инициализация COM-объекта провалилась. Нам нужно разработать отказоустойчивую процедуру открытия файла:
Функция ПолучитьТаблицуДанныхБезопасно(ПутьКФайлуИзделий) Экспорт
// Создаем временный файл
ВремКаталог = КаталогВременныхФайлов() + "temp_" + Новый УникальныйИдентификатор() + "\";
СоздатьКаталог(ВремКаталог);
ДвоичныеДанные = ПолучитьИзВременногоХранилища(ПутьКФайлуИзделий);
ЛокальныйПуть = ВремКаталог + "import_data.xls";
ДвоичныеДанные.Записать(ЛокальныйПуть);
ExcelApp = Неопределено;
Попытка
ExcelApp = Новый COMОбъект("Excel.Application");
ExcelApp.Visible = Ложь;
ExcelApp.DisplayAlerts = Ложь;
Исключение
ВызватьИсключение "Не удалось запустить Microsoft Excel. Убедитесь, что приложение установлено на компьютере.";
КонецПопытки;
Попытка
Workbook = ExcelApp.Workbooks.Open(ЛокальныйПуть, Ложь, Истина); // Открываем только для чтения
Sheet = Workbook.ActiveSheet;
Исключение
ExcelApp.Quit();
ExcelApp = Неопределено;
ВызватьИсключение "Ошибка при открытии файла шаблона. Возможно, файл заблокирован или поврежден: " + ОписаниеОшибки();
КонецПопытки;
// Передаем управление процедуре заполнения
ТЗ = Новый ТаблицаЗначений;
Попытка
ТЗ = ЗаполнитьТаблицуИзЛиста(Sheet);
Исключение
// Гарантируем закрытие процесса Excel при любых непредвиденных ошибках парсинга
Workbook.Close(Ложь);
ExcelApp.Quit();
ExcelApp = Неопределено;
ВызватьИсключение "Ошибка разбора данных файла: " + ОписаниеОшибки();
КонецПопытки;
// Корректно завершаем сеанс работы с COM
Workbook.Close(Ложь);
ExcelApp.Quit();
ExcelApp = Неопределено;
Возврат ТЗ;
КонецФункции
Последовательное обращение к каждой ячейке через метод Cells(Строка, Колонка) в цикле по тысячам строк работает крайне медленно. На каждую ячейку уходит время на межпроцессный вызов между 1С и Excel. Гораздо эффективнее прочитать всю область данных за один вызов, выгрузив её в массив 1С. Посмотрим на практический пример реализации быстрого импорта:
Функция ЗагрузитьБыстроЧерезМассив(Sheet, НачальнаяСтрока = 22, ВсегоКолонок = 15)
ТЗ = ПолучитьОписаниеТЗ();
// Определяем границы заполненной области листа
ПоследняяСтрока = Sheet.Cells(1, 1).SpecialCells(11).Row;
Если ПоследняяСтрока < НачальнаяСтрока Тогда
Возврат ТЗ; // Файл пуст
КонецЕсли;
// Получаем область данных целиком
ОбластьДанных = Sheet.Range(Sheet.Cells(НачальнаяСтрока, 1), Sheet.Cells(ПоследняяСтрока, ВсегоКолонок));
// Выгружаем данные листа в двумерный COMSafeArray
МассивДанных = ОбластьДанных.Value.Выгрузить();
// В COMSafeArray первое измерение - колонки, второе - строки
КоличествоСтрокМассива = МассивДанных.ПолучитьДлину(1);
КоличествоКолонокМассива = МассивДанных.ПолучитьДлину(0);
Для ИндексСтроки = 0 По КоличествоСтрокМассива - 1 Цикл
// Получаем значения по индексам (колонка, строка)
ЗначениеИтогов = СокрЛП(Строка(МассивДанных.GetValue(0, ИндексСтроки))); // Предположим, итоги в 1 колонке
Если ЗначениеИтогов = "ИТОГО:" Или ЗначениеИтогов = "Всего по накладной:" Тогда
Прервать; // Достигли конца документа
КонецЕсли;
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Артикул = СокрЛП(Строка(МассивДанных.GetValue(1, ИндексСтроки))); // 2 колонка
НоваяСтрока.Количество = Число(МассивДанных.GetValue(7, ИндексСтроки)); // 8 колонка
// Заполняем остальные реквизиты ТЗ...
КонецЦикла;
Возврат ТЗ;
КонецФункции
Использование COM-соединения имеет огромный минус: оно привязано к операционной системе Windows и требует физической установки Microsoft Excel на компьютере пользователя или сервере приложений 1С. Если ваша база работает под управлением Linux, веб-клиента или в облаке "1С:Фреш", COM-объекты работать не будут.
Современным стандартом является чтение файлов Excel напрямую средствами платформы 1С:Предприятие 8. Объект ТабличныйДокумент умеет самостоятельно парсить файлы форматов .xls, .xlsx и .ods. Разберем по шагам, как переписать обработку импорта на полностью кроссплатформенный и безопасный вариант:
Функция ПрочитатьФайлExcelЧерезТабДок(ПутьКФайлу)
ТабДок = Новый ТабличныйДокумент;
Попытка
// Чтение файла напрямую с диска средствами платформы 1С
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
ВызватьИсключение "Не удалось открыть файл. Ошибка: " + ОписаниеОшибки();
КонецПопытки;
ТЗ = ПолучитьОписаниеТЗ();
ВсегоСтрок = ТабДок.ВысотаТаблицы;
НачальнаяСтрока = 22;
// Задаем номера колонок в Табличном Документе (начинаются с 1)
КолонкаНаименование = 2;
КолонкаКоличествоШт = 8;
КолонкаВес = 10;
КолонкаЦена = 11;
КолонкаСумма = 15;
Для НомерСтроки = НачальнаяСтрока По ВсегоСтрок Цикл
// Получаем область ячейки
ТекстИтогов = ТабДок.Область(НомерСтроки, КолонкаНаименование).Текст;
Если Найти(ТекстИтогов, "ИТОГО:") > 0 Или Найти(ТекстИтогов, "Всего по накладной:") > 0 Тогда
Прервать; // Выходим из цикла, если дошли до подвала таблицы
КонецЕсли;
// Читаем значения напрямую из ячеек табличного документа
АртикулЗначение = ТабДок.Область(НомерСтроки, КолонкаНаименование).Текст;
Если ПустаяСтрока(АртикулЗначение) Тогда
Продолжить; // Пропускаем пустые строки-разделители
КонецЕсли;
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Артикул = СокрЛП(АртикулЗначение);
// Преобразуем строковые ячейки в числовые показатели
НоваяСтрока.КоличествоШт = Число(СтрЗаменить(ТабДок.Область(НомерСтроки, КолонкаКоличествоШт).Текст, Символы.НПП, ""));
НоваяСтрока.Количество = Число(СтрЗаменить(ТабДок.Область(НомерСтроки, КолонкаВес).Текст, Символы.НПП, ""));
НоваяСтрока.ЦенаЗакупка = Число(СтрЗаменить(ТабДок.Область(НомерСтроки, КолонкаЦена).Текст, Символы.НПП, ""));
НоваяСтрока.СтоимостьЗакупочная = Число(СтрЗаменить(ТабДок.Область(НомерСтроки, КолонкаСумма).Текст, Символы.НПП, ""));
// Заполняем прочие реквизиты из параметров по умолчанию
НоваяСтрока.СтранаПроисхождения = "РОССИЯ";
КонецЦикла;
Возврат ТЗ;
КонецФункции
Использование этого кода позволяет навсегда забыть об ошибке 0x800a03ec и связанных с ней проблемах отсутствия установленного пакета Microsoft Office. Файл парсится за доли секунды, сохраняя работоспособность вашей конфигурации как на Windows, так и на операционных системах семейства Linux.