При разработке и интеграции внешних решений в 1С:Предприятие разработчики часто сталкиваются со следующей проблемой: на стороне клиента внешняя компонента подключается и работает стабильно при многократных вызовах, но при переносе этого кода на сервер повторная инициализация компоненты в рамках одного и того же сеанса вызывает критическую ошибку. Первый запуск проходит успешно, а при втором создании объекта система выдает сбой, исправить который помогает только полный перезапуск рабочих процессов или сеансов связи.
В этой статье мы подробно разберем, почему возникает такое различие в поведении платформы на клиенте и сервере, проанализируем внутренние механизмы кэширования библиотек рабочим процессом rphost.exe и рассмотрим несколько эффективных способов решения этой проблемы.
Для того чтобы найти правильное решение, давайте сначала проанализируем физику процесса взаимодействия сервера 1С с динамическими библиотеками (DLL). Здесь играют роль два ключевых фактора:
ПодключитьВнешнююКомпоненту(), рабочий процесс сервера rphost.exe загружает DLL-файл в свое адресное пространство. Важно понимать, что когда мы пишем в коде Объект = Неопределено; или пытаемся очистить переменную, 1С освобождает ссылку на объект, но не выгружает саму библиотеку DLL из памяти процесса. Она остается там до тех пор, пока рабочий процесс сервера не будет перезапущен. Если внутри библиотеки не реализован корректный многопоточный доступ или повторный вызов инициализации вызывает внутренний конфликт, повторные попытки создать объект внутри того же процесса rphost.exe приведут к ошибке.USR1CV8) работает в жестко ограниченном окружении. У этой учетной записи нет прав на запись в ветку реестра HKEY_CLASSES_ROOT, из-за чего динамическая регистрация COM-классов «на лету» сервером блокируется операционной системой.Новый COMОбъект(...) гарантированно приведет к ошибке на сервере.Перейдем к практическим методам решения этой проблемы.
Если используемая вами библиотека спроектирована как классический COM-сервер, лучшим решением будет полностью отказаться от использования метода ПодключитьВнешнююКомпоненту() на сервере и зарегистрировать DLL в операционной системе один раз.
Разберем этот процесс по шагам:
regsvr32. Обратите внимание на разрядность сервера 1С! Если у вас установлен 64-битный сервер 1С, вам необходимо регистрировать именно 64-битную версию DLL-библиотеки с помощью 64-битной версии утилиты:
regsvr32.exe "C:\PathToComponent\YourComponent64.dll"
После того как компонента успешно зарегистрирована в реестре Windows, нам больше не нужно динамически подключать ее в коде 1С. Мы можем сразу инициализировать объект напрямую через его программный идентификатор (ProgID):
// Прямое создание зарегистрированного COM-объекта на сервере
Попытка
Компонента = Новый COMОбъект("ProgID.ИмяКомпоненты");
// Работа с методами компоненты
Компонента.ВыполнитьДействие();
Исключение
ЗаписьЖурналаРегистрации("Ошибка подключения компоненты",
УровеньЖурналаРегистрации.Ошибка, , ,
ОписаниеОшибки());
КонецПопытки;
Этот способ полностью решает проблему с правами доступа и кэшированием, обеспечивая стабильную многократную работу в любых сеансах без утечек памяти. Для обращения к внешним библиотекам без их предварительной регистрации в операционной системе можно использовать компоненту для запуска .NET сборок без регистрации в реестре.
Если ваша внешняя компонента разработана по современной технологии Native API (не требует регистрации в реестре Windows), то использовать COMОбъект для ее создания категорически нельзя. Подключение и инициализация должны выглядеть следующим образом:
Новый, указывая тип компоненты.Рассмотрим пример кода:
ИмяКомпоненты = "ИмяВнешнейКомпоненты";
ПутьКФайлу = "C:\Components\NativeComponent.dll";
// Подключаем компоненту, если она еще не подключена
Если Не ПодключитьВнешнююКомпоненту(ПутьКФайлу, ИмяКомпоненты, ТипВнешнейКомпоненты.Native) Тогда
ВызватьИсключение "Не удалось подключить Native-компоненту!";
КонецЕсли;
// Создаем объект штатным конструктором 1С
Попытка
ОбъектКомпоненты = Новый("AddIn." + ИмяКомпоненты + ".ИмяКласса");
// Вызов методов объекта
ОбъектКомпоненты.ВыполнитьОперацию();
Исключение
ЗаписьЖурналаРегистрации("Ошибка создания Native-объекта",
УровеньЖурналаРегистрации.Ошибка, , ,
ОписаниеОшибки());
КонецПопытки;
Начиная с версий платформы 1С:Предприятие 8.3.20, разработчики добавили системную возможность изолировать работу внешних компонент от основного процесса 1С. Это нововведение кардинально решает проблему зависания и конфликтов библиотек в памяти сервера.
При использовании параметра ТипПодключенияВнешнейКомпоненты.Изолированно компонента загружается в отдельный, независимый процесс операционной системы. Если внутри компоненты произойдет сбой или она заблокирует память, это никак не повлияет на стабильность процесса rphost.exe. При уничтожении объекта в 1С изолированный процесс завершается, полностью освобождая занятые ресурсы и DLL.
Посмотрим на пример использования этого метода:
// Подключение компоненты в изолированном режиме (доступно с 8.3.20)
РезультатПодключения = ПодключитьВнешнююКомпоненту(
"ОбщийМакет.МояКомпонента",
"MyComponent",
ТипВнешнейКомпоненты.Native,
ТипПодключенияВнешнейКомпоненты.Изолированно
);
Если РезультатПодключения Тогда
Объект = Новый("AddIn.MyComponent.MyClass");
Объект.Действие();
КонецЕсли;
Если вы вынуждены работать на старых версиях платформы 1С (ниже 8.3.20), компоненту невозможно зарегистрировать как полноценный COM-объект в системе, а процесс rphost.exe намертво блокирует файл DLL в памяти при первом вызове, можно применить программный обходной путь.
Суть метода заключается в том, что перед каждым вызовом серверного метода мы копируем исходный файл библиотеки во временный каталог под новым уникальным именем (используя, например, уникальный идентификатор GUID) и подключаем его. Таким образом, для процесса rphost.exe это будет каждый раз новая библиотека, и ошибка блокировки возникать не будет.
Рассмотрим, как это реализовать на практике:
// Генерация уникального пути для обхода кэширования процесса rphost
УникальноеИмя = Строка(Новый УникальныйИдентификатор()) + ".dll";
ВременныйПуть = КаталогВременныхФайлов() + УникальноеИмя;
// Получаем бинарные данные компоненты из макета и записываем во временный файл
БинарныеДанные = ПолучитьОбщийМакет("ИмяМакетаСКомпонентой").Получить();
БинарныеДанные.Записать(ВременныйПуть);
// Подключаем уникальную копию библиотеки
ИмяПодключения = "Имя_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор()), "-", "");
Если ПодключитьВнешнююКомпоненту(ВременныйПуть, ИмяПодключения, ТипВнешнейКомпоненты.Native) Тогда
Попытка
Объект = Новый("AddIn." + ИмяПодключения + ".ИмяКласса");
Объект.ВыполнитьРаботу();
Исключение
ЗаписьЖурналаРегистрации("Ошибка выполнения", УровеньЖурналаРегистрации.Предупреждение, , , ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
// Обратите внимание: временные файлы нужно периодически очищать при старте/завершении сеансов
Важное предупреждение: Использование этого метода приводит к постепенному накоплению временных DLL-файлов в каталоге временных файлов сервера 1С и росту потребления оперативной памяти процессом rphost.exe. Рекомендуется использовать этот подход только в качестве временной меры, отдавая предпочтение Решению 1 или Решению 3.