Как основная конфигурация и расширение 1С вызывают код друг друга
Содержание
Процедуру из расширения 1С вызывают двумя путями. Из кода самого расширения экспортные методы основной конфигурации доступны напрямую: ОбщегоНазначения.ЗначениеРеквизитаОбъекта(...) пишется так же, как в типовом модуле. А вот основная конфигурация про расширение «не знает», поэтому к его коду обращаются либо через перехват метода (&После, &Вместо), либо динамически: проверить, что модуль есть в метаданных, и получить его через Вычислить().
Если написать в типовом модуле Расш1_Сервис.Пересчитать() напрямую, синтаксический контроль в конфигураторе ответит Переменная не определена (Расш1_Сервис). Разберём, как правильно обратиться к функции расширения из любой точки: из модуля формы, общего модуля, обработки и извне по HTTP.
Как устроено взаимодействие конфигурации и расширения
Расширение (файл .cfe) применяется поверх основной конфигурации при запуске сеанса. В работающем сеансе получается одна «сборка»: объекты конфигурации плюс объекты всех активных расширений. Поэтому видимость кода несимметрична.
| Откуда вызываем | Куда | Как |
|---|---|---|
| Расширение | Общий модуль конфигурации | Напрямую, по имени модуля, если метод экспортный |
| Расширение | Модуль формы или объекта конфигурации | Заимствовать объект и перехватить его метод |
| Конфигурация | Общий модуль расширения | Перехват «пустого» метода-точки или динамический вызов |
| Внешняя система | Код расширения | HTTP-сервис в расширении |
Главное правило: основная конфигурация не должна зависеть от того, подключено ли расширение. Расширение можно отключить, удалить, загрузить другую версию. Код конфигурации обязан пережить это без ошибок.
Ещё одна деталь: в базе может быть несколько расширений, и все они перехватывают методы одной «сборки». Если два расширения повесили &После на один и тот же метод, сработают оба. Поэтому перехватчик не должен рассчитывать, что он единственный: читайте и дополняйте результат, а не затирайте его без нужды. С &Вместо осторожнее — без ПродолжитьВызов() он отключает и исходный метод, и логику, которую туда добавили другие расширения.
Расширения конфигурации появились в платформе 8.3.6. Всё ниже относится к 8.3 с интерфейсом «Такси».
Создаём общий модуль-сервис в расширении
Удобно держать всю логику расширения в одном-двух общих модулях. Формы, перехватчики и HTTP-сервисы только вызывают их. Такой модуль работает как внутренний API: один вход, понятные параметры, одно место для правок.
Порядок действий
- Откройте конфигуратор и меню
Конфигурация → Расширения конфигурации. - Выберите своё расширение и откройте его дерево объектов.
- В ветке «Общие модули» добавьте новый модуль. Имя должно начинаться с префикса расширения, например
Расш1_Сервис. - В свойствах модуля отметьте, где он будет работать (см. таблицу в следующем разделе).
- Напишите экспортные методы и сохраните.
- Обновите конфигурацию базы данных для расширения (
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_РассчитатьСкидкуи перетащите её в командную панель. - Обработчики в модуле формы расширения:
&НаКлиенте
Процедура Расш1_РассчитатьСкидку(Команда)
Расш1_РассчитатьСкидкуНаСервере();
КонецПроцедуры
&НаСервере
Процедура Расш1_РассчитатьСкидкуНаСервере()
Объект.Расш1_Скидка = Расш1_Сервис.РассчитатьСкидку(
Объект.СуммаДокумента, Объект.Контрагент.Код);
КонецПроцедуры
Здесь вызов прямой: форма принадлежит расширению, модуль Расш1_Сервис ей виден. Реквизиты СуммаДокумента и Расш1_Скидка условные — подставьте свои. Собственный реквизит в заимствованном документе тоже добавляется в расширении.
Дополнение проведения
Заимствуйте модуль объекта документа и повесьте &После("ОбработкаПроведения"). В перехватчике вызывайте метод сервиса, который добавляет записи в нужный регистр. Параметры перехватчика — те же Отказ и РежимПроведения.
Внешняя обработка
Внешняя обработка не входит ни в конфигурацию, ни в расширение. Из её серверного модуля зовите модуль расширения через ОбщийМодульРасширения() или ОбщегоНазначения.ОбщийМодуль(). Так обработка не сломается в базе, где расширения нет.
Вызов из другой программы по HTTP
Расширение может содержать собственный HTTP-сервис. Обработчик его метода получает HTTPСервисЗапрос, разбирает параметры и вызывает Расш1_Сервис. Сервис публикуется на веб-сервере вместе с базой. Внешняя система обращается по адресу публикации, не зная, что код живёт в расширении.
Если вызов всё равно не срабатывает
- 🔍 Проверьте, что расширение активно и применяется в режиме 1С:Предприятие:
Администрирование → Печатные формы, отчеты и обработки → Расширенияв БП 3.0 и других конфигурациях на БСП. - 🔁 После загрузки новой версии
.cfeоткрытые сеансы работают со старой. Пользователю нужно перезайти. - 🛡️ Если расширение подключено в безопасном режиме, ему запрещены часть операций: работа с файлами, COM, внешние компоненты. Код может падать не из-за вызова, а из-за ограничений внутри метода.
- 🧩 Поставьте точку останова в перехватчике. Если отладчик туда не заходит, перехват не применился: сверьте имя метода в аннотации и набор параметров.
Частые вопросы
Можно ли из одного расширения вызвать код другого расширения?
Метаданные.ОбщиеМодули и получить его через Вычислить(). Прямую ссылку синтаксический контроль не пропустит.Нужно ли заимствовать общий модуль конфигурации, чтобы вызвать его метод из расширения?
Обязательно ли перехватчику быть экспортным?
&После или &Вместо вызывает платформа, а не ваш код. Имя у него должно быть уникальным в модуле, поэтому добавляют префикс.Как из &Вместо выполнить исходный метод?
ПродолжитьВызов() с теми же параметрами. Для функции результат ПродолжитьВызов() можно изменить и вернуть через Возврат.