Вызов клиентского кода из серверного модуля 1С: рабочие способы
Содержание
Напрямую вызвать клиентскую процедуру с сервера в 1С нельзя: серверный код не видит клиентский контекст, и платформа такого вызова не предусматривает. Рабочих обходов четыре. Сервер возвращает результат, а клиент сам решает, что запустить. Клиент опрашивает сервер обработчиком ожидания. Внешняя компонента на клиенте генерирует внешнее событие. Или подсистема «Серверные оповещения» из БСП доставляет сообщение.
Для 90% задач хватает первого способа: серверная функция возвращает структуру, клиентская процедура разбирает её и вызывает нужный код. Опрос нужен для долгих расчётов в фоне. Внешние события и оповещения — когда инициатор не пользователь, а другой сеанс или внешняя система. Всё ниже относится к платформе 8.3 и управляемым формам.
Почему из серверного модуля нельзя вызвать клиентский код
Клиент и сервер в 1С:Предприятии 8.3 — разные процессы. Тонкий клиент работает
на компьютере пользователя, а серверный код выполняет рабочий процесс rphost на сервере 1С. В файловом варианте оба живут на одной машине, но модель вызовов та же.
Вызов всегда идёт в одну сторону:
- 🟢 клиент → сервер: процедура с директивой
&НаСервереили функция общего модуля с флажком «Вызов сервера»; - 🔴 сервер → клиент: такого вызова нет.
Пока сервер работает, клиент ждёт ответа синхронно. У сервера нет ссылки на окно, форму или сеанс клиента. Поэтому код вида МойКлиентскийМодуль.ПоказатьОкно() в серверной процедуре не пройдёт даже синтаксический контроль: модуль с флажком «Клиент (управляемое приложение)» без флажка «Сервер» на сервере просто не виден.
По той же причине на сервере недоступны ПоказатьПредупреждение(), ОткрытьФорму(), Оповестить(), работа с буфером обмена и локальными файлами пользователя.
Про «волшебные» методы
В интернете встречаются советы вызвать некий DirectExecute или передать на клиент строку и выполнить её через Выполнить(). Метода DirectExecute в синтакс-помощнике нет. А Выполнить() со строкой, пришедшей с сервера, — это дыра в безопасности: любой, кто сможет подменить данные, исполнит произвольный код у пользователя.
Не исполняйте на клиенте строки кода, сформированные на сервере. Передавайте имя действия и параметры, а сам код держите в клиентском модуле.
Сравнение способов вызвать клиентскую процедуру
| Способ | Когда подходит | Ограничения |
|---|---|---|
| Возврат результата в форму | Сервер посчитал — клиент показал окно, сообщение, открыл форму | Работает только в ответ на действие пользователя |
| Обработчик ожидания + фоновое задание | Долгий расчёт, нельзя «вешать» интерфейс | Задержка до интервала опроса, лишние серверные вызовы |
| Внешнее событие от компоненты | Нужна реакция на событие извне без действий пользователя | Нужна внешняя компонента, установленная на клиенте |
| Серверные оповещения БСП | Один сеанс должен уведомить другие сеансы | Только в конфигурациях на свежей БСП |
| HTTP-сервис + опрос | Событие приходит из внешней системы | Сервис принимает запрос, но до клиента данные доходят опросом |
Способ 1. Вернуть команду с сервера и вызвать процедуру на клиенте
Самый надёжный приём для управляемых форм. Клиентская процедура вызывает серверную функцию, та возвращает «что сделать дальше», и клиент выполняет нужную процедуру сам.
- В модуле формы создайте клиентский обработчик команды.
- Из него вызовите функцию
&НаСервереили&НаСервереБезКонтекста. - На сервере соберите результат в структуру: флаги, тексты, имя формы, параметры.
- После возврата разберите структуру на клиенте и вызовите клиентские процедуры.
&НаКлиенте
Процедура Проверить(Команда)
Результат = ПроверитьНаСервере();
Если Результат.Предупреждения.Количество() > 0 Тогда
ПоказатьПредупреждение(, СтрСоединить(Результат.Предупреждения, Символы.ПС));
КонецЕсли;
Если Результат.Действие = "ОткрытьОтчет" Тогда
ОткрытьФорму(Результат.ИмяФормы, Результат.ПараметрыФормы, ЭтотОбъект);
ИначеЕсли Результат.Действие = "ОбновитьСписки" Тогда
ОповеститьОбИзменении(Результат.Ссылка);
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПроверитьНаСервере()
Результат = Новый Структура;
Результат.Вставить("Действие", "");
Результат.Вставить("ИмяФормы", "");
Результат.Вставить("ПараметрыФормы", Новый Структура);
Результат.Вставить("Ссылка", Неопределено);
Результат.Вставить("Предупреждения", Новый Массив);
// серверная логика: запросы, проверки, запись
Если Объект.Товары.Количество() = 0 Тогда
Результат.Предупреждения.Добавить("В документе нет строк товаров.");
Иначе
Результат.Действие = "ОбновитьСписки";
Результат.Ссылка = Объект.Ссылка;
КонецЕсли;
Возврат Результат;
КонецФункции
Возвращайте только примитивы, ссылки, структуры и массивы из них. Объекты вроде ДокументОбъект или ТаблицаЗначений на клиент не передаются. Таблицу значений переведите в массив структур или положите во временное хранилище и верните адрес.
Встроенный канал «сервер → клиент»
У платформы есть два механизма, которые фактически доставляют данные с сервера на клиент без вашего кода:
- 📨
СообщениеПользователю(Новый СообщениеПользователю, методСообщить()): текст, созданный на сервере, появится в окне сообщений формы после возврата управления клиенту; - 🧾 реквизиты формы: всё, что серверная процедура с контекстом изменила в реквизитах, приедет на клиент вместе с ответом.
Второй пункт удобен: сервер выставляет реквизит-флаг, а клиент после вызова проверяет его и запускает свою процедуру.
Нужно, чтобы после серверной записи обновилась открытая форма другого объекта? На клиенте вызовите Оповестить("ИмяСобытия", Параметр), а в той форме обработайте событие ОбработкаОповещения. Сам Оповестить() — клиентский метод, поэтому вызывайте его после возврата с сервера.
Когда серверу нужен ответ пользователя
Частый случай: сервер в середине проверки обнаружил проблему и «хочет спросить» пользователя. Спросить с сервера нельзя, поэтому разбейте операцию на два вызова:
- Первый серверный вызов проверяет данные и возвращает текст вопроса.
- Клиент показывает
ПоказатьВопрос()и передаёт в негоНовый ОписаниеОповещения("ПослеОтвета", ЭтотОбъект). - Клиентская процедура
ПослеОтвета(Ответ, ДополнительныеПараметры)получает выбор пользователя. - Если ответ «Да», она делает второй серверный вызов, который завершает операцию.
Так сохраняется немодальность интерфейса, а серверный код не ждёт пользователя.
Способ 2. Обработчик ожидания: клиент сам спрашивает сервер
Если серверная операция длится минуты, держать клиента в синхронном вызове нельзя: интерфейс замрёт, а при долгом простое возможен разрыв соединения. Правильная схема:
- Клиент вызывает сервер, тот запускает фоновое задание и сразу возвращает его идентификатор.
- Клиент подключает обработчик ожидания через
ПодключитьОбработчикОжидания(). - Каждые N секунд обработчик спрашивает сервер о состоянии задания.
- Когда задание завершено, клиент забирает результат из временного хранилища и вызывает нужную клиентскую процедуру.
Пример в модуле формы. Реквизиты формы: АдресРезультата (строка) и ИдентификаторЗадания (уникальный идентификатор).
&НаКлиенте
Процедура Рассчитать(Команда)
ЗапуститьРасчетНаСервере();
ПодключитьОбработчикОжидания("ПроверитьРасчет", 2, Ложь);
КонецПроцедуры
&НаСервере
Процедура ЗапуститьРасчетНаСервере()
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("РасчетыСервер.РассчитатьОстатки", ПараметрыЗадания, , "Расчёт остатков");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьРасчет() Экспорт
Состояние = СостояниеЗадания(ИдентификаторЗадания);
Если Состояние = "Выполняется" Тогда
Возврат;
КонецЕсли;
ОтключитьОбработчикОжидания("ПроверитьРасчет");
Если Состояние = "Завершено" Тогда
ПоказатьРезультат(ПолучитьИзВременногоХранилища(АдресРезультата));
Иначе
ПоказатьПредупреждение(, "Расчёт завершился с ошибкой. Подробности — в журнале регистрации.");
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Возврат "Ошибка";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Возврат "Выполняется";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат "Завершено";
КонецЕсли;
Возврат "Ошибка";
КонецФункции
&НаКлиенте
Процедура ПоказатьРезультат(Данные)
// здесь ваш клиентский код: открыть форму, вывести таблицу, показать итог
КонецПроцедуры
В серверном общем модуле РасчетыСервер экспортная процедура РассчитатьОстатки(АдресРезультата) в конце делает ПоместитьВоВременноеХранилище(Данные, АдресРезультата). Так результат попадает по адресу, который знает форма.
Если в конфигурации есть БСП
Типовые конфигурации на Библиотеке стандартных подсистем не требуют писать опрос вручную. Серверная часть — общий модуль ДлительныеОперации (например, ВыполнитьВФоне или ВыполнитьФункцию). Клиентская — ДлительныеОперацииКлиент.ОжидатьЗавершение(): ей передают описание оповещения, и по завершении задания платформа вызовет указанную клиентскую процедуру. Подсистема сама наращивает интервал опроса и показывает индикатор.
Интервал опроса подбирайте под задачу. Каждый тик — это серверный вызов. 50 пользователей с опросом раз в секунду дают 3000 вызовов в минуту на пустом месте.
Способ 3. Внешние события и серверные оповещения
Первые два способа работают, только пока пользователь что-то запустил сам. Иногда нужно наоборот: событие возникло вне формы, а клиент должен отреагировать.
Внешнее событие от компоненты
Внешняя компонента (Native API) загружается на клиенте и умеет генерировать внешнее событие. Платформа передаёт его в обработчики:
- в модуле управляемого приложения —
ОбработкаВнешнегоСобытия(Источник, Событие, Данные); - в форме — обработчик события формы
ВнешнееСобытиес теми же параметрами.
&НаКлиенте
Процедура ВнешнееСобытие(Источник, Событие, Данные)
Если Источник = "МояКомпонента" И Событие = "НовыйЗаказ" Тогда
ОткрытьНовыйЗаказ(Данные);
КонецЕсли;
КонецПроцедуры
Так работают сканеры штрихкодов и другое торговое оборудование. Чтобы инициатором был сервер, компонента должна слушать канал, в который пишет серверная сторона: сокет, очередь сообщений, файл. Это уже интеграционная разработка, и компоненту под задачу пишут на C++.
Подсистема «Серверные оповещения»
В свежих версиях БСП есть подсистема «Серверные оповещения». Серверный код отправляет оповещение выбранным адресатам: пользователям или сеансам. Клиент получает его и вызывает зарегистрированный обработчик. Доставка идёт периодической проверкой, а при подключённой системе взаимодействия — быстрее.
Если подсистема есть в вашей конфигурации, найдите общий модуль СерверныеОповещения в дереве конфигурации и посмотрите комментарии к экспортным процедурам. Там описаны параметры отправки и порядок регистрации клиентского обработчика.
Не меняйте общие модули БСП в типовой конфигурации на поддержке. Свой обработчик подключайте через расширение, иначе обновление превратится в ручное сравнение и объединение.
Веб-клиент и HTTP-сервисы: что меняется
Для веб-клиента правила те же: клиентский код выполняется в браузере, серверный — на сервере 1С, вызова «сервер → клиент» нет. Схемы со структурой результата и обработчиком ожидания работают без изменений.
Отличия касаются клиентских возможностей:
- 🌐 внешние компоненты в браузере работают только через расширение для работы с 1С:Предприятием и только если компонента собрана под нужный браузер;
- 🌐
Выполнить()со строкой кода в веб-клиенте недоступен, так что «обходной путь» через него не сработает даже технически; - 🌐 доступ к локальным файлам и программам ограничен.
HTTP-сервис — это вход на сервер для внешних систем: сайта, мобильного приложения, другой программы. Сам он ничего не отправляет на клиент. Типовая связка:
- Внешняя система вызывает HTTP-сервис.
- Обработчик сервиса записывает событие в регистр сведений (например, «ВходящиеСобытия»: пользователь, тип, данные, признак «Прочитано»).
- Клиент обработчиком ожидания раз в 30–60 секунд проверяет новые записи для текущего пользователя.
- Найдя запись, клиент вызывает свою процедуру и отмечает событие прочитанным.
Тот же регистр удобен и для уведомлений между сеансами, если подсистемы «Серверные оповещения» нет.
Типичные ошибки при вызове клиентского кода с сервера
Ещё одна частая ошибка — забыть ОтключитьОбработчикОжидания(). Опрос продолжается после завершения задания и бьёт сервер до закрытия формы. Отключайте обработчик сразу, как только состояние перестало быть «Выполняется», и в событии ПриЗакрытии формы.
Частые вопросы
Можно ли из модуля объекта или проведения вызвать клиентскую процедуру?
СообщениеПользователю, а реакцию оформите в форме после записи.Чем отличаются &НаСервере и &НаСервереБезКонтекста?
&НаСервере передаёт на сервер данные формы и возвращает изменения реквизитов. &НаСервереБезКонтекста работает только с параметрами и быстрее, потому что форма не передаётся.