Почему не загружается внешняя компонента в фоновом задании и как это исправить?

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

При переносе клиентского кода во внешние обработки или фоновые (регламентные) задания разработчики часто сталкиваются с ситуацией, когда метод ЗагрузитьВнешнююКомпоненту или ПодключитьВнешнююКомпоненту генерирует ошибку во время выполнения регламентного задания. При этом интерактивный запуск того же самого кода под обычным пользователем отрабатывает без нареканий.

Разберем типичный пример проблемного кода инициализации компоненты отправки СМС:


ПутьКомпонента = КаталогПрограммы() + "SMS\SMSXml.dll";
Попытка
    ЗагрузитьВнешнююКомпоненту(ПутьКомпонента);
    СМСХмл = Новый("Addin.SMSXml");
Исключение
    ЗаписьЖурналаРегистрации("Контрагенты.SMS", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
    СМСХмл = -1;
    Возврат;
КонецПопытки;

При выполнении данного фрагмента в контексте фонового задания платформа генерирует исключение: «Ошибка при вызове метода контекста (ЗагрузитьВнешнююКомпоненту): Ошибка при загрузке внешней компоненты».

Выясним фундаментальные причины такого поведения и подробно разберем все способы решения проблемы.


Причины сбоя при загрузке компоненты на сервере

Проанализируем архитектуру работы «1С:Предприятия» и операционной системы, чтобы понять, почему один и тот же вызов работает на клиенте, но падает на сервере:

  1. Несоответствие разрядности процессов (32 vs 64 бит): Клиентское приложение 1cv8.exe чаще всего является 32-разрядным (x86) и без проблем загружает в свое адресное пространство 32-битные библиотеки DLL. Фоновые и регламентные задания всегда выполняются на стороне сервера приложений в контексте рабочего процесса rphost.exe. Если сервер приложений 64-битный (x64), операционная система физически блокирует прямую загрузку 32-битной библиотеки DLL в память 64-битного процесса.
  2. Устаревшая технология COM (IInitDone): Метод ЗагрузитьВнешнююКомпоненту предназначен для старых библиотек, разработанных по технологии COM. На стороне сервера «1С:Предприятия» штатно поддерживается загрузка только компонент, созданных по технологии Native API. Компоненты COM ориентированы на интерактивный клиентский сеанс Windows.
  3. Особенности путей и прав доступа: Функция КаталогПрограммы() на сервере вернет путь к каталогу исполняемых файлов сервера (например, C:\Program Files\1cv8\...), а не клиентской части. Кроме того, фоновое задание запускается от имени системной службы (по умолчанию USR1CV8), которая может не иметь прав на чтение указанных каталогов или доступ к пользовательским веткам реестра HKEY_CLASSES_ROOT\WOW6432Node.

Вариант 1. Обертка 32-битной DLL через службу COM+ (DllSurrogate)

Если внешняя библиотека существует только в 32-битном варианте, исходного кода нет и перекомпилировать её невозможно, лучшим выходом будет вынесение работы с компонентой в отдельный суррогатный процесс Windows (dllhost.exe).

Разберем по шагам настройку сервера Windows:

  1. Открываем консоль управления компонентами: нажимаем Win + R, вводим dcomcnfg или comexp.msc и переходим по дереву: Службы компонентов -> Компьютеры -> Мой компьютер -> Приложения COM+.
  2. Создаем новое приложение: нажимаем правой кнопкой мыши на ветку Приложения COM+, выбираем Создать -> Приложение, нажимаем «Далее» и выбираем пункт «Установить новое приложение».
  3. Задаем понятное имя (например, SMS_Surrogate_x86) и обязательно указываем способ активации: «Серверное приложение» (Server application). Это заставит библиотеку запускаться в изолированном 32-битном процессе dllhost.exe.
  4. На этапе указания удостоверения выбираем учетную запись, от имени которой работает служба сервера 1С (обычно USR1CV8), либо локальную системную учетную запись с достаточными правами.
  5. В созданном приложении раскрываем ветку, нажимаем правой кнопкой мыши на Компоненты -> Создать -> Компонент, выбираем «Установка новых компонентов» и указываем путь к файлу SMSXml.dll.
  6. В свойствах созданного приложения COM+ на вкладке «Безопасность» отключаем принудительную авторизацию для вызовов или добавляем учетную запись службы 1С в список разрешенных пользователей.

Теперь в программном коде 1С не требуется вызывать ЗагрузитьВнешнююКомпоненту. Обращение к объекту выполняется через стандартный COMОбъект:


Попытка
    // Создаем объект через системный реестр и серверный процесс COM+
    СМСХмл = Новый COMОбъект("SMSXml.SMSXmlClass");
    // Вызываем методы компоненты
    СМСХмл.Отправить("79991234567", "Текст сообщения");
Исключение
    ЗаписьЖурналаРегистрации("Отправка SMS", УровеньЖурналаРегистрации.Ошибка,,, 
        "Ошибка инициализации COM-объекта: " + ОписаниеОшибки());
КонецПопытки;

Вариант 2. Переход на прямые HTTP-запросы (без внешних компонент)

В большинстве случаев сторонние DLL для отправки SMS, работы с фискальными регистраторами или сервисами доставки представляют собой простую обертку над стандартным веб-API шлюза (REST/XML/JSON). Использование внешних библиотек создает лишние зависимости от операционной системы и разрядности.

Посмотрим на пример отправки запроса через встроенные механизмы платформы HTTPСоединение и HTTPЗапрос:


СерверSMS = "api.smsintel.ru";
РесурсSMS = "/xml/";

Соединение = Новый HTTPСоединение(СерверSMS, 443, , , , 30, Новый ЗащищенноеСоединениеOpenSSL());

Запрос = Новый HTTPЗапрос(РесурсSMS);
Запрос.Заголовки.Вставить("Content-Type", "application/xml; charset=utf-8");

// Формируем тело запроса напрямую в XML
ТелоXML = 
"<?xml version=""1.0"" encoding=""utf-8"" ?>" +
"<request>" +
"   <security><login value=""user"" /><password value=""pass"" /></security>" +
"   <message type=""sms""><sender>Company</sender><text>Ваш код: 1234</text><abonent phone=""79991234567"" /></message>" +
"</request>";

Запрос.УстановитьТелоИзСтроки(ТелоXML, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);

Попытка
    Ответ = Соединение.ОтправитьДляОбработки(Запрос);
    Если Ответ.КодСостояния = 200 Тогда
        РезультатТекст = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
        // Обрабатываем ответ сервиса
    Иначе
        ЗаписьЖурналаРегистрации("Отправка SMS", УровеньЖурналаРегистрации.Предупреждение,,,
            "Ошибка HTTP: " + Ответ.КодСостояния);
    КонецЕсли;
Исключение
    ЗаписьЖурналаРегистрации("Отправка SMS", УровеньЖурналаРегистрации.Ошибка,,,
        "Не удалось отправить запрос: " + ОписаниеОшибки());
КонецПопытки;

Такой подход полностью универсален: он работает на любой разрядности сервера 1С (x86 и x64), под управлением любых операционных систем (Windows и Linux) и не требует регистрации файлов в реестре.


Вариант 3. Использование современных компонент по технологии Native API

Если для решения задачи обязательно требуется функционал скомпилированной библиотеки (например, криптография, работа со специализированным оборудованием или драйверами), следует использовать компоненты стандарта Native API.

Рассмотрим, как правильно подключать такие компоненты из макета базы данных:


Попытка
    // Подключаем Native-компоненту из общего макета (содержащего ZIP-архив с манифестом)
    РезультатПодключения = ПодключитьВнешнююКомпоненту("ОбщийМакет.КомпонентаSMS", "SMSNative", ТипВнешнейКомпоненты.Native);
    
    Если РезультатПодключения Тогда
        ОбъектSMS = Новый("AddIn.SMSNative.SMSClass");
        // Выполняем действия с объектом
    Иначе
        ВызватьИсключение "Не удалось инициализировать Native-компоненту.";
    КонецЕсли;
Исключение
    ЗаписьЖурналаРегистрации("Отправка SMS", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;

Начиная с версии платформы «1С:Предприятие» 8.3.21, для серверного вызова доступен параметр ТипПодключенияВнешнейКомпоненты.Изолированно. В этом режиме внешняя библиотека выполняется в отдельном процессе операционной системы, защищая рабочий процесс rphost.exe от возможных сбоев и утечек памяти.

← На главную