При работе с тяжелыми внешними компонентами, особенно в области криптографии или сложной обработки данных, разработчики часто сталкиваются с проблемой производительности. Если компонента, выполненная по технологии Native API, инициализируется несколько секунд, выполнять её подключение при каждом серверном вызове становится недопустимым. Однако попытки «запомнить» объект компоненты в контексте сервера стандартными средствами 1С разбиваются об архитектурные ограничения платформы. Проанализируем, почему это происходит, и рассмотрим рабочие стратегии решения этой задачи.
Для начала разберем, почему привычные механизмы хранения данных не подходят для объектов внешних компонент. В 1С:Предприятие для сохранения состояния между вызовами сервера обычно используются Параметры сеанса или Временное хранилище. Однако эти механизмы жестко завязаны на процессе сериализации.
Сериализация — это процесс превращения объекта в поток байтов (например, в XML или внутренний формат 1С), который можно передать по сети или сохранить в базу данных. Внешние компоненты Native API представляют собой скомпилированный бинарный код (DLL), работающий напрямую с оперативной памятью. Платформа 1С не знает внутренней структуры этой памяти и не может «упаковать» живой объект C++ в байтовый поток. Именно поэтому при попытке записи компоненты в параметр сеанса возникает ошибка: объект не поддерживает сериализацию.
Проанализируем жизненный цикл в рабочем процессе rphost. На сервере любой объект живет ровно столько, сколько существует переменная, которая на него ссылается, в рамках одного серверного вызова. Как только выполнение кода возвращается на клиент, контекст серверного вызова уничтожается, счетчик ссылок обнуляется, и платформа принудительно вызывает метод Done у компоненты, выгружая её из памяти.
Если специфика вашей задачи позволяет выполнять операции на стороне пользователя (например, подписание документов в тонком клиенте), то самым простым и эффективным способом будет использование глобальной переменной в Модуле управляемого приложения.
Рассмотрим алгоритм реализации:
Пример кода в модуле управляемого приложения:
Перем ГлобальнаяКомпонентаКриптографии Экспорт;
Процедура ПриНачалеРаботыСистемы()
Если ПодключитьВнешнююКомпоненту("ОбщийМакет.КриптоБиблиотека", "Crypto", ТипВнешнейКомпоненты.Native) Тогда
ГлобальнаяКомпонентаКриптографии = Новый("AddIn.Crypto.MainClass");
КонецЕсли;
КонецПроцедуры
В этом случае компонента будет жить в памяти клиентского процесса ровно столько, сколько открыт сеанс пользователя. Повторная инициализация не потребуется.
Интересной особенностью платформы является то, что COM-объекты ведут себя иначе. Как было замечено в обсуждении, COM-компоненты иногда удается хранить там, где Native-компоненты выдают ошибку. Это связано с механизмом маршалинга. Если компонента зарегистрирована как Out-of-Process сервер (EXE), она живет в своем собственном адресном пространстве независимо от 1С. Платформа лишь хранит ссылку на этот процесс. Если у вас есть возможность использовать COM-версию библиотеки, это может стать временным решением, однако технология COM считается устаревшей и плохо масштабируемой в среде Linux.
Если работа с компонентой должна происходить строго на сервере (например, регламентное задание по шифрованию архивов), и клиентское хранение невозможно, единственным надежным архитектурным решением будет вынос компоненты за пределы процесса rphost. Рассмотрим создание небольшого прокси-приложения (сервиса), которое будет держать компоненту постоянно загруженной.
Для взаимодействия 1С с таким сервисом можно использовать несколько протоколов:
HTTPСоединение.Такой подход гарантирует, что падение или перезагрузка рабочего процесса 1С не приведет к потере состояния компоненты, а сама «тяжелая» инициализация будет выполнена всего один раз при старте сервиса.
Часто звучит идея использовать «бесконечное» фоновое задание. Рассмотрим этот вариант. Мы можем запустить фоновое задание, которое в цикле будет ожидать появления данных в определенной таблице или очереди. Внутри этого задания компонента может быть инициализирована и храниться в переменной модуля.
Процедура ВыполнитьОбработкуКриптографии() Экспорт
// Инициализация один раз при старте задания
ПодключитьВнешнююКомпоненту(...);
Компонента = Новый(...);
Пока Истина Цикл
// Проверяем наличие заданий в базе данных
Задание = ПолучитьНовоеЗаданиеИзОчереди();
Если Задание <> Неопределено Тогда
Результат = Компонента.Шифровать(Задание.Данные);
СохранитьРезультат(Задание, Результат);
КонецЕсли;
// Пауза, чтобы не нагружать процессор
Приостановить(1);
КонецЦикла;
КонецПроцедуры
Важное ограничение: Фоновое задание не сможет «отдать» готовый объект компоненты в другой сеанс. Оно может только принять данные, обработать их внутри себя и вернуть результат через хранилище. Это хороший вариант для асинхронных задач, но он не решает проблему, если вам нужен немедленный ответ в пользовательском сеансе здесь и сейчас.
Если вы решите реализовать совместное использование одной компоненты разными сеансами (через внешний сервис или статические переменные в самой DLL), необходимо учитывать вопрос Thread-safety (потокобезопасности). Большинство криптографических библиотек на C++ не рассчитаны на одновременное обращение из разных потоков к одному экземпляру объекта. Если два пользователя одновременно попытаются вызвать метод шифрования, это может привести к повреждению памяти и аварийному завершению процесса rphost или вашего сервиса. При реализации пула обращений обязательно используйте механизмы блокировок (мьютексы) или создавайте пул из нескольких инициализированных экземпляров компоненты.
Подведем итог: Прямого способа «заставить» 1С хранить Native-компоненту на сервере между вызовами не существует из-за ограничений сериализации. Наиболее правильным путем является либо перенос компоненты на клиент (глобальная переменная модуля приложения), либо создание внешнего сервиса-обертки, который возьмет на себя управление жизненным циклом тяжелой библиотеки.