Обращение к экспортным процедурам модуля объекта из внешнего кода

· 9 мин

Содержание

Чтобы вызвать процедуру из модуля объекта, у неё должно стоять ключевое слово Экспорт, а сам объект нужно сначала получить в переменную: Объект = Ссылка.ПолучитьОбъект(), и только потом писать Объект.ИмяПроцедуры(). Без Экспорт платформа такую процедуру снаружи не видит — отсюда большинство ошибок «процедура с указанным именем не определена».

Второй частый промах — попытка дёрнуть метод прямо у ссылки (Ссылка.Метод()) или прямо из клиентского кода управляемой формы. Модуль объекта исполняется на сервере, и работать можно только с объектом, а не со ссылкой. Ниже — все рабочие способы вызова, с кодом и границами применимости.

Ключевое слово Экспорт — условие видимости

Процедуры и функции модуля объекта делятся на два вида: локальные (видны только внутри модуля) и экспортные (видны снаружи). Разницу задаёт одно слово в конце строки объявления.

Процедура ОбновитьЦены() Экспорт
    Для Каждого Строка Из Товары Цикл
        Строка.Цена = ПолучитьБазовуюЦену(Строка.Номенклатура);
    КонецЦикла;
КонецПроцедуры

Теперь из формы, общего модуля или обработки эту процедуру можно вызвать по имени объекта:

Объект = Ссылка.ПолучитьОбъект();
Объект.ОбновитьЦены();
Объект.Записать();

Если убрать Экспорт, при вызове снаружи вы получите ошибку Процедура или функция с указанным именем не определена (ОбновитьЦены). Внутри самого модуля объекта та же процедура будет вызываться без проблем — Экспорт нужен только для вызова извне.

💡 Имена методов в 1С регистронезависимы, но платформа различает процедуры и функции. Если метод возвращает значение, объявляйте его как Функция … КонецФункции и вызывайте через присваивание: Итог = Объект.РассчитатьИтог().

Параметры в метод передаются как обычно. По умолчанию параметр передаётся по ссылке — метод может его изменить, и изменение увидит вызывающий код. Если нужно защитить переданную переменную от изменения, в объявлении параметра ставят Знач:

Процедура ПрименитьСкидку(Знач Процент) Экспорт
    Для Каждого Строка Из Товары Цикл
        Строка.Сумма = Строка.Сумма * (1 - Процент / 100);
    КонецЦикла;
КонецПроцедуры

Вызов с параметром:

Объект = Ссылка.ПолучитьОбъект();
Объект.ПрименитьСкидку(10);
Объект.Записать();

Как получить сам объект перед вызовом метода

Вызвать метод можно только у объекта (ДокументОбъект, СправочникОбъект и т. п.), а не у ссылки. Получить объект можно двумя путями — в зависимости от того, работаете вы с существующим элементом или создаёте новый.

Существующий объект берут из ссылки:

Объект = Ссылка.ПолучитьОбъект();

Новый объект создают через менеджер:

НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
НовыйЭлемент  = Справочники.Номенклатура.СоздатьЭлемент();

У обоих вариантов доступны экспортные методы их модуля объекта. Важно помнить про жизненный цикл:

  • 📌 ПолучитьОбъект() при каждом вызове возвращает новый экземпляр из базы. Если вызвать его дважды для одной ссылки, получите два разных объекта в памяти.
  • 📌 Изменения, сделанные методом, живут только в этом экземпляре. Чтобы они попали в базу, объект нужно записать: Объект.Записать() (или Записать(РежимЗаписиДокумента.Проведение) для проведения).
  • 📌 Не вызывайте ПолучитьОбъект() в цикле по одной и той же ссылке — это лишние обращения к базе. Получите объект один раз до цикла.

Метод, который меняет данные и сам вызывает Записать(), в рабочей базе выполняйте осознанно: запись документа запускает проведение и движения по регистрам. Перед массовым вызовом таких методов по множеству объектов проверяйте логику на копии базы.

Типичный сценарий — создать документ , заполнить его экспортным методом и записать одной транзакцией:

Документ = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
Документ.Дата = ТекущаяДата();
Документ.Организация = Организация;
Документ.Контрагент = Контрагент;
Документ.ЗаполнитьПоЗаказу(Заказ); // экспортный метод модуля объекта
Документ.Записать(РежимЗаписиДокумента.Проведение);

Обратите внимание: реквизиты «шапки» можно задать прямо снаружи, а сложное заполнение табличных частей логично спрятать в метод модуля объекта — тогда одна и та же логика будет работать и при интерактивном вводе, и при программном создании.

Предопределённые обработчики событий вручную не вызывают

В модуле объекта, помимо ваших экспортных процедур, есть процедуры-обработчики событий: ПередЗаписью, ПриЗаписи, ОбработкаПроведения, ОбработкаУдаленияПроведения, ОбработкаЗаполнения и другие. Их вызывает сама платформа в нужный момент жизненного цикла объекта.

Прямой вызов такого обработчика по имени — частая ошибка новичка:

// Так делать не нужно:
Объект.ОбработкаПроведения(Отказ, РежимПроведения);

При обычной записи Объект.Записать() платформа сама вызовет ОбработкаПроведения с корректными параметрами и внутри собственной транзакции. Ручной вызов передаст неверный контекст и, скорее всего, приведёт к незаполненным движениям или ошибке.

💡 Если логику из обработчика события нужно переиспользовать, вынесите её в отдельную экспортную процедуру модуля объекта и вызывайте уже её — и из обработчика события, и из внешнего кода. Это чистый способ без «ручного» запуска системных событий.

Откуда можно вызвать: клиент, сервер и контекст модуля

Модуль объекта компилируется и исполняется на сервере. Директив &НаКлиенте в нём нет. Поэтому напрямую из клиентского кода управляемой формы метод объекта вызвать нельзя — сначала нужно перейти в серверный контекст.

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

&НаКлиенте
Процедура ОбновитьЦеныКоманда(Команда)
    ОбновитьЦеныНаСервере(Объект.Ссылка);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ОбновитьЦеныНаСервере(Ссылка)
    Объект = Ссылка.ПолучитьОбъект();
    Объект.ОбновитьЦены();
    Объект.Записать();
КонецПроцедуры

Клиентская процедура только инициирует вызов, а всю работу с объектом ведёт серверная. Если метод должен поменять реквизиты открытой в форме записи, используйте &НаСервере без БезКонтекста и работайте с реквизитом Объект формы через РеквизитФормыВЗначение / ЗначениеВРеквизитФормы.

Отдельно стоит модуль менеджера. Его экспортные методы вызывают не через объект, а через сам менеджер:

Результат = Документы.РеализацияТоваровУслуг.ПроверитьДоступность(Номенклатура);

Правило простое: логика, привязанная к конкретному экземпляру (пересчёт строк, заполнение по данным), живёт в модуле объекта; общая логика вида «найти», «проверить», «построить» — в модуле менеджера.

Из общего модуля вызов выглядит так же — сначала объект, потом метод:

// Общий модуль, серверный контекст
Процедура ОбновитьПачкуДокументов(МассивСсылок) Экспорт
    Для Каждого Ссылка Из МассивСсылок Цикл
        Объект = Ссылка.ПолучитьОбъект();
        Объект.ОбновитьЦены();
        Объект.Записать();
    КонецЦикла;
КонецПроцедуры

Общий модуль удобен тем, что один раз написанную обёртку можно звать из любой формы, обработки и регламентного задания.

Вызов процедуры по имени, заданному строкой

Когда имя метода заранее неизвестно и лежит в переменной (например, приходит из настроек или таблицы правил), применяют операторы Выполнить() и Вычислить().

Выполнить() исполняет строку как оператор — подходит для процедур:

ИмяМетода = "ОбновитьЦены";
Выполнить("Объект." + ИмяМетода + "()");

Вычислить() вычисляет выражение и возвращает значение — подходит для функций:

ИмяФункции = "РассчитатьИтог";
Итог = Вычислить("Объект." + ИмяФункции + "()");

Способ мощный, но у него есть цена:

  • ❗ Код в строке не проверяется на этапе компиляции — опечатка всплывёт только при выполнении.
  • ❗ Работает медленнее прямого вызова, так как разбор строки идёт в рантайме.
  • ❗ На клиенте и в безопасном режиме действуют ограничения; строку, собранную из внешних данных, подставлять опасно.

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

Для Каждого Строка Из ТаблицаПравил Цикл
    Объект = Строка.Ссылка.ПолучитьОбъект();
    Выполнить("Объект." + Строка.ИмяМетода + "()");
    Объект.Записать();
КонецЦикла;

Используйте Выполнить() и Вычислить() только тогда, когда имя метода действительно неизвестно до запуска. Если имён конечный набор, надёжнее обычный Если … Тогда с прямыми вызовами — компилятор проверит каждый.

Асинхронный вызов после интерактивного действия

Модуль объекта выполняется синхронно на сервере. «Асинхронность» в этом сценарии появляется на клиенте: когда перед вызовом метода нужно задать пользователю вопрос, дождаться выбора файла или закрытия формы. Для этого платформа использует объект ОписаниеОповещения.

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

&НаКлиенте
Процедура ПересчитатьКоманда(Команда)
    Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект);
    ПоказатьВопрос(Оповещение, "Пересчитать суммы по документу?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры

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

Здесь важны два требования к процедуре-обработчику:

  1. Она должна быть объявлена с Экспорт — иначе ОписаниеОповещения её не найдёт.
  2. Она живёт в модуле формы или в неглобальном общем модуле с клиентским контекстом, а не в модуле объекта.

Если в обработчик нужно передать данные (ссылку, флаг, значение поля), их кладут в третий параметр конструктора — ДополнительныеПараметры. Внутри обработчика они придут вторым аргументом:

ПараметрыОбработчика = Новый Структура("Ссылка, ТолькоПроверка", Объект.Ссылка, Ложь);
Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект, ПараметрыОбработчика);

То есть сам метод объекта по-прежнему вызывается синхронно на сервере; асинхронным становится лишь клиентский сценарий вокруг него.

Чем способы отличаются друг от друга

Выбор зависит от того, где находится код вызова и известно ли имя метода заранее.

СпособКогда применятьОграничения
Объект.Метод()Имя известно, метод экспортныйТолько на сервере, нужен полученный объект
Менеджер.Метод()Общая логика без конкретного экземпляраМетод должен быть в модуле менеджера
Выполнить() / Вычислить()Имя метода в переменнойНет проверки при компиляции, медленнее, безопасный режим
Через ОписаниеОповещенияПосле вопроса, выбора файла, закрытия формыТолько клиентский обработчик, обязателен Экспорт

На практике большинство вызовов — это первая строка таблицы: получили объект, вызвали экспортный метод, записали. Остальные способы подключают, когда появляется дополнительное условие: динамическое имя или интерактив перед действием.

Если метод записывает объект и вызывается в цикле по многим ссылкам, держите каждую итерацию самодостаточной: получайте свежий объект, вызывайте метод, записывайте и переходите к следующему. Не накапливайте изменения в одном объекте между итерациями — после Записать() работать с тем же экземпляром дальше можно, но для другой ссылки нужен свой ПолучитьОбъект().

Обработка ошибок при вызове метода

Метод объекта может завершиться исключением: не хватило данных, сработала проверка заполнения, запись упала на блокировке. Чтобы один сбойный объект не прерывал обработку всей пачки, оборачивайте вызов в Попытка.

Попытка
    Объект = Ссылка.ПолучитьОбъект();
    Объект.ОбновитьЦены();
    Объект.Записать();
Исключение
    ЗаписьЖурналаРегистрации(
        "ОбновлениеЦен",
        УровеньЖурналаРегистрации.Ошибка,
        , Ссылка,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Текст ошибки берите из ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) — это актуальный способ для платформы 8.3 (устаревший ОписаниеОшибки() даёт меньше деталей).

Частые ошибки при вызове и их причины:

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

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

Нужен ли Экспорт, если я вызываю процедуру внутри того же модуля объекта?
Нет. Внутри модуля объекта все процедуры видны друг другу без Экспорт. Ключевое слово нужно только для вызова снаружи — из формы, общего модуля или обработки.
Чем модуль объекта отличается от модуля менеджера при вызове?
Методы модуля объекта вызывают через полученный объект (Объект.Метод()), они работают с данными конкретного экземпляра. Методы модуля менеджера вызывают через менеджер (Документы.Вид.Метод()) — это общая логика, не привязанная к одной записи.
Почему после вызова метода изменения не сохранились в базе?
Метод меняет объект только в оперативной памяти. Чтобы записать изменения, после вызова нужен Объект.Записать() — для документа при необходимости с режимом проведения.
Можно ли вызвать метод объекта напрямую из кнопки на клиенте?
Нет. Клиентский код не имеет доступа к серверному модулю объекта. Создайте серверную процедуру (&НаСервере или &НаСервереБезКонтекста), получите в ней объект и вызовите метод, а с клиента зовите уже эту процедуру.
Как вызвать метод, если его имя хранится в переменной?
Через оператор Выполнить() для процедуры или Вычислить() для функции: Выполнить("Объект." + ИмяМетода + "()"). Учтите, что такой код не проверяется компилятором и медленнее прямого вызова.