Как основная конфигурация и расширение 1С вызывают код друг друга

· 9 мин

Содержание

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

Если написать в типовом модуле Расш1_Сервис.Пересчитать() напрямую, синтаксический контроль в конфигураторе ответит Переменная не определена (Расш1_Сервис). Разберём, как правильно обратиться к функции расширения из любой точки: из модуля формы, общего модуля, обработки и извне по HTTP.

Как устроено взаимодействие конфигурации и расширения

Расширение (файл .cfe) применяется поверх основной конфигурации при запуске сеанса. В работающем сеансе получается одна «сборка»: объекты конфигурации плюс объекты всех активных расширений. Поэтому видимость кода несимметрична.

Откуда вызываемКудаКак
РасширениеОбщий модуль конфигурацииНапрямую, по имени модуля, если метод экспортный
РасширениеМодуль формы или объекта конфигурацииЗаимствовать объект и перехватить его метод
КонфигурацияОбщий модуль расширенияПерехват «пустого» метода-точки или динамический вызов
Внешняя системаКод расширенияHTTP-сервис в расширении

Главное правило: основная конфигурация не должна зависеть от того, подключено ли расширение. Расширение можно отключить, удалить, загрузить другую версию. Код конфигурации обязан пережить это без ошибок.

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

Расширения конфигурации появились в платформе 8.3.6. Всё ниже относится к 8.3 с интерфейсом «Такси».

Создаём общий модуль-сервис в расширении

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

Порядок действий

  1. Откройте конфигуратор и меню Конфигурация → Расширения конфигурации.
  2. Выберите своё расширение и откройте его дерево объектов.
  3. В ветке «Общие модули» добавьте новый модуль. Имя должно начинаться с префикса расширения, например Расш1_Сервис.
  4. В свойствах модуля отметьте, где он будет работать (см. таблицу в следующем разделе).
  5. Напишите экспортные методы и сохраните.
  6. Обновите конфигурацию базы данных для расширения (F7 в окне расширения) и перезапустите сеанс пользователя.

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

Пример модуля

// Общий модуль расширения Расш1_Сервис
// Свойства: Сервер, Вызов сервера

Функция РассчитатьСкидку(Знач СуммаДокумента, Знач КодКлиента) Экспорт

    Если Не ЗначениеЗаполнено(КодКлиента) Тогда
        ВызватьИсключение "Расш1: не указан код клиента";
    КонецЕсли;

    Если СуммаДокумента >= 100000 Тогда
        Возврат Окр(СуммаДокумента * 0.05, 2);
    КонецЕсли;

    Возврат 0;

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

При сумме 120 000 функция вернёт 6 000, при 80 000 — ноль.

Параметры с Знач передаются по значению. Вызывающий код не получит неожиданных изменений своих переменных. Для сервиса это хорошая привычка.

Экспорт методов и флаги общего модуля

Чтобы метод был виден снаружи модуля, в его объявлении нужно ключевое слово Экспорт. Без него обращение Расш1_Сервис.РассчитатьСкидку() завершится ошибкой Метод объекта не обнаружен (РассчитатьСкидку).

Второе условие — контекст. Общий модуль доступен только там, где это разрешено флагами в его свойствах.

ФлагЧто даёт
СерверМетоды доступны в серверном коде
Вызов сервераМетоды можно вызвать с клиента, исполнение пойдёт на сервере
Клиент (управляемое приложение)Методы работают в клиентском коде форм
Внешнее соединениеДоступ при подключении через COM
ПривилегированныйКод выполняется без проверки прав пользователя
Повторное использование возвращаемых значенийКэш результатов на время вызова или сеанса

Типичная связка для сервиса: «Сервер» + «Вызов сервера». Тогда из &НаКлиенте-процедуры формы можно сразу вызвать Расш1_Сервис.РассчитатьСкидку(...).

Флаг «Привилегированный» ставьте только туда, где без него не обойтись. Все методы такого модуля обходят права, и любой код расширения, вызывающий их, получает полный доступ к данным.

Белый список вместо произвольных строк

Если основная конфигурация будет вызывать расширение по имени метода (см. ниже), заведите в расширении один экспортный «диспетчер» и перечень допустимых методов. Так извне не вызовут то, что не предназначено для вызова.

Функция ВыполнитьКоманду(Знач ИмяКоманды, Знач Параметры) Экспорт

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

    ВызватьИсключение СтрШаблон("Расш1: неизвестная команда %1", ИмяКоманды);

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

Вызов из основной конфигурации: три рабочих способа

Способ 1. Перехват метода конфигурации (рекомендуется)

Расширение умеет встраиваться в существующие методы. Для этого объект заимствуют, а в модуле расширения пишут процедуру с аннотацией.

АннотацияКогда срабатывает
&Перед("Имя")До исходного метода
&После("Имя")После исходного метода
&Вместо("Имя")Вместо исходного; оригинал можно вызвать через ПродолжитьВызов()
&ИзменениеИКонтроль("Имя")Правка текста метода с контролем изменений в типовой

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

// Общий модуль конфигурации ТочкиРасширения (Сервер)
Процедура ПриРасчетеСкидки(СуммаДокумента, КодКлиента, Скидка) Экспорт
    // Тело пустое: логику добавляет расширение
КонецПроцедуры

В расширении заимствуйте модуль ТочкиРасширения (контекстное меню объекта → «Добавить в расширение») и добавьте перехватчик:

&После("ПриРасчетеСкидки")
Процедура Расш1_ПриРасчетеСкидки(СуммаДокумента, КодКлиента, Скидка)
    Скидка = Расш1_Сервис.РассчитатьСкидку(СуммаДокумента, КодКлиента);
КонецПроцедуры

Параметры перехватчика повторяют параметры исходного метода. Результат возвращается через параметр Скидка, он передан по ссылке.

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

Способ 2. Динамический вызов с проверкой метаданных

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

// Общий модуль конфигурации, флаг Сервер
Функция ОбщийМодульРасширения(Знач ИмяМодуля) Экспорт

    Если Метаданные.ОбщиеМодули.Найти(ИмяМодуля) = Неопределено Тогда
        Возврат Неопределено; // расширение не подключено
    КонецЕсли;

    УстановитьБезопасныйРежим(Истина);
    Возврат Вычислить(ИмяМодуля);

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

Вызов:

Модуль = ОбщийМодульРасширения("Расш1_Сервис");
Если Модуль <> Неопределено Тогда
    Скидка = Модуль.РассчитатьСкидку(Сумма, КодКлиента);
КонецЕсли;

Имя сначала сверяется с метаданными, поэтому в Вычислить() не попадёт произвольная строка. В конфигурациях на БСП готовая функция уже есть: ОбщегоНазначения.ОбщийМодуль("Расш1_Сервис"). Она делает то же самое и проверку тоже выполняет.

Способ 3. Выполнить() со строкой кода

Выполнить("Расш1_Сервис.Пересчитать(Документ)");

Работает, но это худший вариант. Опечатки не ловит синтаксический контроль. Строку легко собрать из непроверенных данных. Отладка неудобна. Оставьте его для разовых обработок.

Как выбрать

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

Ошибки при вызове и как их перехватить

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

Шаблон обработки исключения

Вызов чужого кода оборачивайте в Попытка, если сбой расширения не должен останавливать основную операцию. Пишите подробности в журнал регистрации, пользователю показывайте короткий текст.

Скидка = 0;
Модуль = ОбщийМодульРасширения("Расш1_Сервис");
Если Модуль <> Неопределено Тогда
    Попытка
        Скидка = Модуль.РассчитатьСкидку(Сумма, КодКлиента);
    Исключение
        Инфо = ИнформацияОбОшибке();
        ЗаписьЖурналаРегистрации("Расширение.РасчетСкидки",
            УровеньЖурналаРегистрации.Ошибка, , ,
            ПодробноеПредставлениеОшибки(Инфо));
        ОбщегоНазначенияКлиентСервер.СообщитьПользователю(
            "Скидка не рассчитана, подробности в журнале регистрации.");
    КонецПопытки;
КонецЕсли;

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

Не глушите исключения внутри транзакции записи документа. Если расширение упало при проведении, лучше отменить запись целиком, чем провести документ с половиной движений.

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

Практические сценарии

Кнопка на форме документа

Нужно добавить в форму заказа кнопку «Рассчитать скидку».

  1. Заимствуйте форму документа в расширение.
  2. В форме расширения создайте команду Расш1_РассчитатьСкидку и перетащите её в командную панель.
  3. Обработчики в модуле формы расширения:
&НаКлиенте
Процедура Расш1_РассчитатьСкидку(Команда)
    Расш1_РассчитатьСкидкуНаСервере();
КонецПроцедуры

&НаСервере
Процедура Расш1_РассчитатьСкидкуНаСервере()
    Объект.Расш1_Скидка = Расш1_Сервис.РассчитатьСкидку(
        Объект.СуммаДокумента, Объект.Контрагент.Код);
КонецПроцедуры

Здесь вызов прямой: форма принадлежит расширению, модуль Расш1_Сервис ей виден. Реквизиты СуммаДокумента и Расш1_Скидка условные — подставьте свои. Собственный реквизит в заимствованном документе тоже добавляется в расширении.

Дополнение проведения

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

Внешняя обработка

Внешняя обработка не входит ни в конфигурацию, ни в расширение. Из её серверного модуля зовите модуль расширения через ОбщийМодульРасширения() или ОбщегоНазначения.ОбщийМодуль(). Так обработка не сломается в базе, где расширения нет.

Вызов из другой программы по HTTP

Расширение может содержать собственный HTTP-сервис. Обработчик его метода получает HTTPСервисЗапрос, разбирает параметры и вызывает Расш1_Сервис. Сервис публикуется на веб-сервере вместе с базой. Внешняя система обращается по адресу публикации, не зная, что код живёт в расширении.

Перед передачей расширения в рабочую базу
Выполнено: 0 / 6

Если вызов всё равно не срабатывает

  • 🔍 Проверьте, что расширение активно и применяется в режиме 1С:Предприятие: Администрирование → Печатные формы, отчеты и обработки → Расширения в БП 3.0 и других конфигурациях на БСП.
  • 🔁 После загрузки новой версии .cfe открытые сеансы работают со старой. Пользователю нужно перезайти.
  • 🛡️ Если расширение подключено в безопасном режиме, ему запрещены часть операций: работа с файлами, COM, внешние компоненты. Код может падать не из-за вызова, а из-за ограничений внутри метода.
  • 🧩 Поставьте точку останова в перехватчике. Если отладчик туда не заходит, перехват не применился: сверьте имя метода в аннотации и набор параметров.

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

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