Разработка механизмов интеграции и загрузки данных из внешних источников — одна из самых частых задач для программиста 1С. При этом разработчики нередко сталкиваются с ситуацией, когда обработка загрузки данных из файлов Microsoft Excel идеально работает на локальном компьютере (файловая копия базы), но при попытке запустить её в клиент-серверном режиме на рабочей базе выдает ошибку:
«Ошибка при вызове конструктора (ComОбъект) Ex=Новый ComОбъект("Excel.Application") ... reason: -2147221005(0x800401F3): Invalid class string»
В этой статье мы подробно разберем причины возникновения данной ошибки, рассмотрим правильную архитектуру взаимодействия клиента и сервера при работе с внешними файлами, а также изучим альтернативные и более стабильные методы чтения Excel без использования технологии COM.
Для начала проанализируем техническую суть проблемы. Когда код выполняется в процедуре с директивой &НаСервере, конструктор Новый COMОбъект("Excel.Application") пытается инициализировать приложение Microsoft Excel непосредственно на стороне сервера приложений «1С:Предприятие». Ошибка инициализации COM-класса в этом случае происходит по одной из следующих причин:
rphost.exe) не увидит зарегистрированный в 32-битной ветке реестра класс Excel.USR1CV8) или под системной записью LocalSystem. У этих пользователей по умолчанию отсутствуют права на запуск и активацию интерактивных DCOM-приложений, к которым относится Microsoft Excel.Самый простой и архитектурно верный способ избежать проблем с правами на сервере — выполнять чтение файла непосредственно на компьютере пользователя (в контексте &НаКлиенте), где Excel гарантированно установлен и работает под текущим сеансом Windows. Однако здесь разработчики часто совершают другую ошибку: они пытаются прочитать COM-объект на клиенте и передать полученный объект листа Worksheet в серверную процедуру для записи данных.
Попытка передать COM-объект напрямую на сервер приведет к ошибке передачи данных XDTO («Data transfer between client and server error. Invalid value type...»), поскольку COM-объекты не могут быть сериализованы и переданы между контекстами клиента и сервера.
Правильный подход заключается в следующем: мы инициализируем Excel на клиенте, считываем данные в простую структуру (например, в массив структур или таблицу значений) и уже этот безопасный массив передаем на сервер. Давайте разберем этот алгоритм на конкретном примере кода:
&НаКлиенте
Процедура ВыполнитьЗагрузку(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.Фильтр = "Файлы Excel (*.xls;*.xlsx)|*.xls;*.xlsx";
Диалог.Заголовок = "Выберите файл для загрузки";
Если Диалог.Выбрать() Тогда
ПутьКФайлу = Диалог.ПолноеИмяФайла;
// Шаг 1. Читаем данные на клиенте в простой массив структур
МассивДанных = ПрочитатьФайлExcelНаКлиенте(ПутьКФайлу);
Если МассивДанных.Количество() > 0 Тогда
// Шаг 2. Передаем массив на сервер для обработки
СоздатьНоменклатуруНаСервере(МассивДанных);
ПоказатьПредупреждение(, "Загрузка успешно завершена!");
Иначе
ПоказатьПредупреждение(, "Файл пуст или не содержит корректных данных.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция ПрочитатьФайлExcelНаКлиенте(Знач ПутьКФайлу)
МассивРезультатов = Новый Массив;
Попытка
ExcelПриложение = Новый COMОбъект("Excel.Application");
Исключение
ВызватьИсключение "Не удалось запустить Microsoft Excel на локальном компьютере. Возможно, приложение не установлено.";
КонецПопытки;
ExcelПриложение.DisplayAlerts = Ложь;
Попытка
Книга = ExcelПриложение.Workbooks.Open(ПутьКФайлу);
Лист = Книга.Sheets(1); // Работаем с первым листом
// Определяем заполненные строки
ВсегоСтрок = Лист.UsedRange.Rows.Count;
// Начинаем считывание со второй строки (пропускаем шапку таблицы)
Для НомерСтроки = 2 По ВсегоСтрок Цикл
КодАртикула = СокрЛП(Лист.Cells(НомерСтроки, 1).Value);
Наименование = СокрЛП(Лист.Cells(НомерСтроки, 2).Value);
Если ПустаяСтрока(Наименование) Тогда
Продолжить; // Пропускаем пустые строки
КонецЕсли;
// Записываем данные в простую структуру
СтрокаДанных = Новый Структура;
СтрокаДанных.Вставить("Артикул", КодАртикула);
СтрокаДанных.Вставить("Наименование", Наименование);
МассивРезультатов.Добавить(СтрокаДанных);
КонецДля;
Книга.Close(Ложь);
Исключение
ExcelПриложение.Quit();
ВызватьИсключение "Ошибка при чтении файла Excel: " + ОписаниеОшибки();
КонецПопытки;
ExcelПриложение.Quit();
Возврат МассивРезультатов;
КонецФункции
&НаСервере
Процедура СоздатьНоменклатуруНаСервере(МассивДанных)
НачатьТранзакцию();
Попытка
Для Каждого СтрокаДанных Из МассивДанных Цикл
// Проверяем, существует ли уже номенклатура с таким артикулом
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", СтрокаДанных.Артикул);
Если НайденнаяСсылка.Пустая() Тогда
НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НовыйОбъект.Наименование = СтрокаДанных.Наименование;
НовыйОбъект.Артикул = СтрокаДанных.Артикул;
// Заполняем другие необходимые реквизиты по умолчанию
НовыйОбъект.Записать();
КонецЕсли;
КонецДля;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьЛогСобытия("ОшибкаЗагрузки", ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
ВызватьИсключение "Ошибка при записи номенклатуры на сервере. Операция отменена.";
КонецПопытки;
КонецПроцедуры
Если специфика вашей задачи требует запуска загрузки в неинтерактивном режиме (например, в регламентном задании, которое выполняется строго на сервере по расписанию), вам придется настроить операционную систему сервера Windows для работы с COM-объектом Excel. Разберем по шагам, как это сделать:
dcomcnfg.exe через строку выполнения Windows). Если у вас 64-разрядная ОС, а MS Office установлен 32-разрядный, запустите консоль в 32-битном режиме командой: mmc comexp.msc /32.USR1CV8), и предоставьте ему полные права («Локальный запуск», «Локальная активация»).C:\Windows\SysWOW64\config\systemprofile\DesktopC:\Windows\System32\config\systemprofile\DesktopDesktop для пользователя службы сервера 1С.Использование COM-объектов постепенно уходит в прошлое. Этот метод медленный, нестабильный (процессы Excel часто «зависают» в памяти сервера, блокируя файлы) и абсолютно не кроссплатформенный (код не будет работать, если сервер 1С запущен на ОС Linux). Начиная с версии платформы 8.3.6, компания 1С добавила возможность прямого программного чтения файлов форматов .xls, .xlsx и .ods без использования стороннего ПО.
Для реализации этого метода мы передаем файл на сервер через временное хранилище, а затем на сервере считываем его напрямую в объект ТабличныйДокумент. Разберем, как реализовать этот современный подход:
&НаКлиенте
Процедура ЗагрузитьФайлШтатнымиСредствами(Команда)
// Выбор файла пользователем на клиенте
ПараметрыВыбора = Новый ПараметрыДиалогаПомещенияФайла;
ПараметрыВыбора.Фильтр = "Таблицы Excel (*.xlsx; *.xls)|*.xlsx;*.xls";
ПараметрыВыбора.Заголовок = "Выберите файл для загрузки номенклатуры";
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла;
НачатьПомещениеФайлаНаСервер(
Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтотОбъект),
,,,
ПараметрыВыбора,
УникальныйИдентификатор
);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПомещенияФайла(Результат, АдресИлиРезультат, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
// Если пользователь успешно выбрал и передал файл на сервер
Если АдресИлиРезультат <> Неопределено Тогда
// Вызываем серверный метод для парсинга и загрузки
ЗагрузитьИзХранилищаНаСервере(АдресИлиРезультат);
ПоказатьПредупреждение(, "Загрузка номенклатуры успешно выполнена!");
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьИзХранилищаНаСервере(Знач АдресХранилища)
// Создаем имя для временного файла на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("xlsx");
// Получаем двоичные данные файла из хранилища и записываем на диск сервера
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
ДвоичныеДанныеФайла.Записать(ИмяВременногоФайла);
// Инициализируем ТабличныйДокумент 1С
ТабДок = Новый ТабличныйДокумент;
Попытка
// Читаем файл Excel напрямую без использования COM-объектов!
ТабДок.Прочитать(ИмяВременногоФайла, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
УдалитьФайлы(ИмяВременногоФайла);
ВызватьИсключение "Не удалось прочитать файл на сервере. Ошибка: " + ОписаниеОшибки();
КонецПопытки;
// После чтения обязательно удаляем временный файл с диска сервера
УдалитьФайлы(ИмяВременногоФайла);
// Преобразуем ТабличныйДокумент в стандартную ТаблицуЗначений с помощью ПостроителяЗапроса
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТабДок.Область());
Построитель.ЗаполнитьНастройки();
Построитель.Выполнить();
ТаблицаДанных = Построитель.Результат.Выгрузить();
// Обрабатываем полученную таблицу
НачатьТранзакцию();
Попытка
Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
// Предположим, что колонки в Excel называются "Артикул" и "Наименование"
// В ТаблицеДанных имена колонок могут быть автоматически названы как "Ф1", "Ф2" и т.д.
// Проверим заполненность первой колонки (индекс 0)
Артикул = СокрЛП(СтрокаТаблицы[0]);
Наименование = СокрЛП(СтрокаТаблицы[1]);
Если ПустаяСтрока(Наименование) Тогда
Продолжить;
КонецЕсли;
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", Артикул);
Если НайденнаяСсылка.Пустая() Тогда
НоваяНоменклатура = Справочники.Номенклатура.СоздатьЭлемент();
НоваяНоменклатура.Наименование = Наименование;
НоваяНоменклатура.Артикул = Артикул;
НоваяНоменклатура.Записать();
КонецЕсли;
КонецДля;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение "Ошибка транзакции при записи данных: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Использование встроенного метода чтения файлов через ТабличныйДокумент решает проблему раз и навсегда. Программа больше не зависит от установленных версий Microsoft Office на сервере приложений, скорость чтения возрастает в несколько раз, а код становится стабильным и готовым к работе на любых современных серверах 1С под управлением Windows или Linux.