При переносе клиентского кода во внешние обработки или фоновые (регламентные) задания разработчики часто сталкиваются с ситуацией, когда метод ЗагрузитьВнешнююКомпоненту или ПодключитьВнешнююКомпоненту генерирует ошибку во время выполнения регламентного задания. При этом интерактивный запуск того же самого кода под обычным пользователем отрабатывает без нареканий.
Разберем типичный пример проблемного кода инициализации компоненты отправки СМС:
ПутьКомпонента = КаталогПрограммы() + "SMS\SMSXml.dll";
Попытка
ЗагрузитьВнешнююКомпоненту(ПутьКомпонента);
СМСХмл = Новый("Addin.SMSXml");
Исключение
ЗаписьЖурналаРегистрации("Контрагенты.SMS", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
СМСХмл = -1;
Возврат;
КонецПопытки;
При выполнении данного фрагмента в контексте фонового задания платформа генерирует исключение: «Ошибка при вызове метода контекста (ЗагрузитьВнешнююКомпоненту): Ошибка при загрузке внешней компоненты».
Выясним фундаментальные причины такого поведения и подробно разберем все способы решения проблемы.
Проанализируем архитектуру работы «1С:Предприятия» и операционной системы, чтобы понять, почему один и тот же вызов работает на клиенте, но падает на сервере:
1cv8.exe чаще всего является 32-разрядным (x86) и без проблем загружает в свое адресное пространство 32-битные библиотеки DLL. Фоновые и регламентные задания всегда выполняются на стороне сервера приложений в контексте рабочего процесса rphost.exe. Если сервер приложений 64-битный (x64), операционная система физически блокирует прямую загрузку 32-битной библиотеки DLL в память 64-битного процесса.ЗагрузитьВнешнююКомпоненту предназначен для старых библиотек, разработанных по технологии COM. На стороне сервера «1С:Предприятия» штатно поддерживается загрузка только компонент, созданных по технологии Native API. Компоненты COM ориентированы на интерактивный клиентский сеанс Windows.КаталогПрограммы() на сервере вернет путь к каталогу исполняемых файлов сервера (например, C:\Program Files\1cv8\...), а не клиентской части. Кроме того, фоновое задание запускается от имени системной службы (по умолчанию USR1CV8), которая может не иметь прав на чтение указанных каталогов или доступ к пользовательским веткам реестра HKEY_CLASSES_ROOT\WOW6432Node.Если внешняя библиотека существует только в 32-битном варианте, исходного кода нет и перекомпилировать её невозможно, лучшим выходом будет вынесение работы с компонентой в отдельный суррогатный процесс Windows (dllhost.exe).
Разберем по шагам настройку сервера Windows:
dcomcnfg или comexp.msc и переходим по дереву: Службы компонентов -> Компьютеры -> Мой компьютер -> Приложения COM+.SMS_Surrogate_x86) и обязательно указываем способ активации: «Серверное приложение» (Server application). Это заставит библиотеку запускаться в изолированном 32-битном процессе dllhost.exe.USR1CV8), либо локальную системную учетную запись с достаточными правами.SMSXml.dll.Теперь в программном коде 1С не требуется вызывать ЗагрузитьВнешнююКомпоненту. Обращение к объекту выполняется через стандартный COMОбъект:
Попытка
// Создаем объект через системный реестр и серверный процесс COM+
СМСХмл = Новый COMОбъект("SMSXml.SMSXmlClass");
// Вызываем методы компоненты
СМСХмл.Отправить("79991234567", "Текст сообщения");
Исключение
ЗаписьЖурналаРегистрации("Отправка SMS", УровеньЖурналаРегистрации.Ошибка,,,
"Ошибка инициализации COM-объекта: " + ОписаниеОшибки());
КонецПопытки;
В большинстве случаев сторонние 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) и не требует регистрации файлов в реестре.
Если для решения задачи обязательно требуется функционал скомпилированной библиотеки (например, криптография, работа со специализированным оборудованием или драйверами), следует использовать компоненты стандарта Native API.
Рассмотрим, как правильно подключать такие компоненты из макета базы данных:
Попытка
// Подключаем Native-компоненту из общего макета (содержащего ZIP-архив с манифестом)
РезультатПодключения = ПодключитьВнешнююКомпоненту("ОбщийМакет.КомпонентаSMS", "SMSNative", ТипВнешнейКомпоненты.Native);
Если РезультатПодключения Тогда
ОбъектSMS = Новый("AddIn.SMSNative.SMSClass");
// Выполняем действия с объектом
Иначе
ВызватьИсключение "Не удалось инициализировать Native-компоненту.";
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("Отправка SMS", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
Начиная с версии платформы «1С:Предприятие» 8.3.21, для серверного вызова доступен параметр ТипПодключенияВнешнейКомпоненты.Изолированно. В этом режиме внешняя библиотека выполняется в отдельном процессе операционной системы, защищая рабочий процесс rphost.exe от возможных сбоев и утечек памяти.