Вызов клиентского кода из серверного модуля 1С: рабочие способы

· 9 мин

Содержание

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

Для 90% задач хватает первого способа: серверная функция возвращает структуру, клиентская процедура разбирает её и вызывает нужный код. Опрос нужен для долгих расчётов в фоне. Внешние события и оповещения — когда инициатор не пользователь, а другой сеанс или внешняя система. Всё ниже относится к платформе 8.3 и управляемым формам.

Почему из серверного модуля нельзя вызвать клиентский код

Клиент и сервер в 1С:Предприятии 8.3 — разные процессы. Тонкий клиент работает на компьютере пользователя, а серверный код выполняет рабочий процесс rphost на сервере 1С. В файловом варианте оба живут на одной машине, но модель вызовов та же.

Вызов всегда идёт в одну сторону:

  • 🟢 клиент → сервер: процедура с директивой &НаСервере или функция общего модуля с флажком «Вызов сервера»;
  • 🔴 сервер → клиент: такого вызова нет.

Пока сервер работает, клиент ждёт ответа синхронно. У сервера нет ссылки на окно, форму или сеанс клиента. Поэтому код вида МойКлиентскийМодуль.ПоказатьОкно() в серверной процедуре не пройдёт даже синтаксический контроль: модуль с флажком «Клиент (управляемое приложение)» без флажка «Сервер» на сервере просто не виден.

По той же причине на сервере недоступны ПоказатьПредупреждение(), ОткрытьФорму(), Оповестить(), работа с буфером обмена и локальными файлами пользователя.

Про «волшебные» методы

В интернете встречаются советы вызвать некий DirectExecute или передать на клиент строку и выполнить её через Выполнить(). Метода DirectExecute в синтакс-помощнике нет. А Выполнить() со строкой, пришедшей с сервера, — это дыра в безопасности: любой, кто сможет подменить данные, исполнит произвольный код у пользователя.

Не исполняйте на клиенте строки кода, сформированные на сервере. Передавайте имя действия и параметры, а сам код держите в клиентском модуле.

Сравнение способов вызвать клиентскую процедуру

СпособКогда подходитОграничения
Возврат результата в формуСервер посчитал — клиент показал окно, сообщение, открыл формуРаботает только в ответ на действие пользователя
Обработчик ожидания + фоновое заданиеДолгий расчёт, нельзя «вешать» интерфейсЗадержка до интервала опроса, лишние серверные вызовы
Внешнее событие от компонентыНужна реакция на событие извне без действий пользователяНужна внешняя компонента, установленная на клиенте
Серверные оповещения БСПОдин сеанс должен уведомить другие сеансыТолько в конфигурациях на свежей БСП
HTTP-сервис + опросСобытие приходит из внешней системыСервис принимает запрос, но до клиента данные доходят опросом
Что инициирует вызов клиентского кода?

Способ 1. Вернуть команду с сервера и вызвать процедуру на клиенте

Самый надёжный приём для управляемых форм. Клиентская процедура вызывает серверную функцию, та возвращает «что сделать дальше», и клиент выполняет нужную процедуру сам.

  1. В модуле формы создайте клиентский обработчик команды.
  2. Из него вызовите функцию &НаСервере или &НаСервереБезКонтекста.
  3. На сервере соберите результат в структуру: флаги, тексты, имя формы, параметры.
  4. После возврата разберите структуру на клиенте и вызовите клиентские процедуры.
&НаКлиенте
Процедура Проверить(Команда)

	Результат = ПроверитьНаСервере();

	Если Результат.Предупреждения.Количество() > 0 Тогда
		ПоказатьПредупреждение(, СтрСоединить(Результат.Предупреждения, Символы.ПС));
	КонецЕсли;

	Если Результат.Действие = "ОткрытьОтчет" Тогда
		ОткрытьФорму(Результат.ИмяФормы, Результат.ПараметрыФормы, ЭтотОбъект);
	ИначеЕсли Результат.Действие = "ОбновитьСписки" Тогда
		ОповеститьОбИзменении(Результат.Ссылка);
	КонецЕсли;

КонецПроцедуры

&НаСервере
Функция ПроверитьНаСервере()

	Результат = Новый Структура;
	Результат.Вставить("Действие", "");
	Результат.Вставить("ИмяФормы", "");
	Результат.Вставить("ПараметрыФормы", Новый Структура);
	Результат.Вставить("Ссылка", Неопределено);
	Результат.Вставить("Предупреждения", Новый Массив);

	// серверная логика: запросы, проверки, запись
	Если Объект.Товары.Количество() = 0 Тогда
		Результат.Предупреждения.Добавить("В документе нет строк товаров.");
	Иначе
		Результат.Действие = "ОбновитьСписки";
		Результат.Ссылка = Объект.Ссылка;
	КонецЕсли;

	Возврат Результат;

КонецФункции

Возвращайте только примитивы, ссылки, структуры и массивы из них. Объекты вроде ДокументОбъект или ТаблицаЗначений на клиент не передаются. Таблицу значений переведите в массив структур или положите во временное хранилище и верните адрес.

Встроенный канал «сервер → клиент»

У платформы есть два механизма, которые фактически доставляют данные с сервера на клиент без вашего кода:

  • 📨 СообщениеПользователю (Новый СообщениеПользователю, метод Сообщить()): текст, созданный на сервере, появится в окне сообщений формы после возврата управления клиенту;
  • 🧾 реквизиты формы: всё, что серверная процедура с контекстом изменила в реквизитах, приедет на клиент вместе с ответом.

Второй пункт удобен: сервер выставляет реквизит-флаг, а клиент после вызова проверяет его и запускает свою процедуру.

Нужно, чтобы после серверной записи обновилась открытая форма другого объекта? На клиенте вызовите Оповестить("ИмяСобытия", Параметр), а в той форме обработайте событие ОбработкаОповещения. Сам Оповестить() — клиентский метод, поэтому вызывайте его после возврата с сервера.

Когда серверу нужен ответ пользователя

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

  1. Первый серверный вызов проверяет данные и возвращает текст вопроса.
  2. Клиент показывает ПоказатьВопрос() и передаёт в него Новый ОписаниеОповещения("ПослеОтвета", ЭтотОбъект).
  3. Клиентская процедура ПослеОтвета(Ответ, ДополнительныеПараметры) получает выбор пользователя.
  4. Если ответ «Да», она делает второй серверный вызов, который завершает операцию.

Так сохраняется немодальность интерфейса, а серверный код не ждёт пользователя.

Способ 2. Обработчик ожидания: клиент сам спрашивает сервер

Если серверная операция длится минуты, держать клиента в синхронном вызове нельзя: интерфейс замрёт, а при долгом простое возможен разрыв соединения. Правильная схема:

  1. Клиент вызывает сервер, тот запускает фоновое задание и сразу возвращает его идентификатор.
  2. Клиент подключает обработчик ожидания через ПодключитьОбработчикОжидания().
  3. Каждые N секунд обработчик спрашивает сервер о состоянии задания.
  4. Когда задание завершено, клиент забирает результат из временного хранилища и вызывает нужную клиентскую процедуру.

Пример в модуле формы. Реквизиты формы: АдресРезультата (строка) и ИдентификаторЗадания (уникальный идентификатор).

&НаКлиенте
Процедура Рассчитать(Команда)
	ЗапуститьРасчетНаСервере();
	ПодключитьОбработчикОжидания("ПроверитьРасчет", 2, Ложь);
КонецПроцедуры

&НаСервере
Процедура ЗапуститьРасчетНаСервере()
	АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
	ПараметрыЗадания = Новый Массив;
	ПараметрыЗадания.Добавить(АдресРезультата);
	Задание = ФоновыеЗадания.Выполнить("РасчетыСервер.РассчитатьОстатки", ПараметрыЗадания, , "Расчёт остатков");
	ИдентификаторЗадания = Задание.УникальныйИдентификатор;
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьРасчет() Экспорт
	Состояние = СостояниеЗадания(ИдентификаторЗадания);
	Если Состояние = "Выполняется" Тогда
		Возврат;
	КонецЕсли;
	ОтключитьОбработчикОжидания("ПроверитьРасчет");
	Если Состояние = "Завершено" Тогда
		ПоказатьРезультат(ПолучитьИзВременногоХранилища(АдресРезультата));
	Иначе
		ПоказатьПредупреждение(, "Расчёт завершился с ошибкой. Подробности — в журнале регистрации.");
	КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)
	Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
	Если Задание = Неопределено Тогда
		Возврат "Ошибка";
	ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
		Возврат "Выполняется";
	ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
		Возврат "Завершено";
	КонецЕсли;
	Возврат "Ошибка";
КонецФункции

&НаКлиенте
Процедура ПоказатьРезультат(Данные)
	// здесь ваш клиентский код: открыть форму, вывести таблицу, показать итог
КонецПроцедуры

В серверном общем модуле РасчетыСервер экспортная процедура РассчитатьОстатки(АдресРезультата) в конце делает ПоместитьВоВременноеХранилище(Данные, АдресРезультата). Так результат попадает по адресу, который знает форма.

Если в конфигурации есть БСП

Типовые конфигурации на Библиотеке стандартных подсистем не требуют писать опрос вручную. Серверная часть — общий модуль ДлительныеОперации (например, ВыполнитьВФоне или ВыполнитьФункцию). Клиентская — ДлительныеОперацииКлиент.ОжидатьЗавершение(): ей передают описание оповещения, и по завершении задания платформа вызовет указанную клиентскую процедуру. Подсистема сама наращивает интервал опроса и показывает индикатор.

Интервал опроса подбирайте под задачу. Каждый тик — это серверный вызов. 50 пользователей с опросом раз в секунду дают 3000 вызовов в минуту на пустом месте.

Способ 3. Внешние события и серверные оповещения

Первые два способа работают, только пока пользователь что-то запустил сам. Иногда нужно наоборот: событие возникло вне формы, а клиент должен отреагировать.

Внешнее событие от компоненты

Внешняя компонента (Native API) загружается на клиенте и умеет генерировать внешнее событие. Платформа передаёт его в обработчики:

  • в модуле управляемого приложения — ОбработкаВнешнегоСобытия(Источник, Событие, Данные);
  • в форме — обработчик события формы ВнешнееСобытие с теми же параметрами.
&НаКлиенте
Процедура ВнешнееСобытие(Источник, Событие, Данные)
	Если Источник = "МояКомпонента" И Событие = "НовыйЗаказ" Тогда
		ОткрытьНовыйЗаказ(Данные);
	КонецЕсли;
КонецПроцедуры

Так работают сканеры штрихкодов и другое торговое оборудование. Чтобы инициатором был сервер, компонента должна слушать канал, в который пишет серверная сторона: сокет, очередь сообщений, файл. Это уже интеграционная разработка, и компоненту под задачу пишут на C++.

Подсистема «Серверные оповещения»

В свежих версиях БСП есть подсистема «Серверные оповещения». Серверный код отправляет оповещение выбранным адресатам: пользователям или сеансам. Клиент получает его и вызывает зарегистрированный обработчик. Доставка идёт периодической проверкой, а при подключённой системе взаимодействия — быстрее.

Если подсистема есть в вашей конфигурации, найдите общий модуль СерверныеОповещения в дереве конфигурации и посмотрите комментарии к экспортным процедурам. Там описаны параметры отправки и порядок регистрации клиентского обработчика.

Не меняйте общие модули БСП в типовой конфигурации на поддержке. Свой обработчик подключайте через расширение, иначе обновление превратится в ручное сравнение и объединение.

Веб-клиент и HTTP-сервисы: что меняется

Для веб-клиента правила те же: клиентский код выполняется в браузере, серверный — на сервере 1С, вызова «сервер → клиент» нет. Схемы со структурой результата и обработчиком ожидания работают без изменений.

Отличия касаются клиентских возможностей:

  • 🌐 внешние компоненты в браузере работают только через расширение для работы с 1С:Предприятием и только если компонента собрана под нужный браузер;
  • 🌐 Выполнить() со строкой кода в веб-клиенте недоступен, так что «обходной путь» через него не сработает даже технически;
  • 🌐 доступ к локальным файлам и программам ограничен.

HTTP-сервис — это вход на сервер для внешних систем: сайта, мобильного приложения, другой программы. Сам он ничего не отправляет на клиент. Типовая связка:

  1. Внешняя система вызывает HTTP-сервис.
  2. Обработчик сервиса записывает событие в регистр сведений (например, «ВходящиеСобытия»: пользователь, тип, данные, признак «Прочитано»).
  3. Клиент обработчиком ожидания раз в 30–60 секунд проверяет новые записи для текущего пользователя.
  4. Найдя запись, клиент вызывает свою процедуру и отмечает событие прочитанным.

Тот же регистр удобен и для уведомлений между сеансами, если подсистемы «Серверные оповещения» нет.

Типичные ошибки при вызове клиентского кода с сервера

Диагностика: выберите симптом

Ещё одна частая ошибка — забыть ОтключитьОбработчикОжидания(). Опрос продолжается после завершения задания и бьёт сервер до закрытия формы. Отключайте обработчик сразу, как только состояние перестало быть «Выполняется», и в событии ПриЗакрытии формы.

Частые вопросы

Можно ли из модуля объекта или проведения вызвать клиентскую процедуру?
Нет. Модуль объекта выполняется на сервере. Передайте флаг или текст через дополнительные свойства объекта либо через СообщениеПользователю, а реакцию оформите в форме после записи.
Чем отличаются &НаСервере и &НаСервереБезКонтекста?
&НаСервере передаёт на сервер данные формы и возвращает изменения реквизитов. &НаСервереБезКонтекста работает только с параметрами и быстрее, потому что форма не передаётся.
Можно ли показать диалог пользователю из фонового задания?
Нет. У фонового задания нет клиента. Пусть задание запишет результат или вопрос в хранилище, а форма покажет диалог, когда опрос увидит завершение.
Как вызвать клиентскую процедуру у другого пользователя?
Только через доставку сообщения в его сеанс: подсистему «Серверные оповещения» или свой регистр событий с опросом на клиенте. Прямого вызова в чужой сеанс нет.
Работает ли обработчик ожидания в свёрнутом окне?
Да, пока приложение запущено. Но при закрытии формы обработчики, подключённые в ней, перестают вызываться.