Вызов процедур основной конфигурации из кода расширения: модули, контекст, права
Содержание
Общий модуль основной конфигурации вызывается из расширения так же, как из самой конфигурации: ИмяМодуля.ИмяМетода(Параметры). Заимствовать модуль для этого не нужно, префикс к его имени не добавляется. Метод должен быть объявлен с ключевым словом Экспорт, а модуль должен быть доступен в том контексте, откуда вы его вызываете: на клиенте, на сервере или через вызов сервера.
Префикс расширения (например, Расш1_) нужен только собственным объектам расширения. Почти все проблемы с обращением к общему модулю из расширения сводятся к трём вещам: неэкспортный метод, неподходящий контекст исполнения и права пользователя. Ниже разберём каждую, плюс перехват методов и обратный вызов из конфигурации в расширение.
Как расширение видит объекты основной конфигурации
Расширение — надстройка над конфигурацией. Платформа при запуске накладывает его на основную конфигурацию, и код расширения компилируется в общем пространстве имён. Поэтому все общие модули конфигурации видны коду расширения без дополнительных действий.
В дереве расширения объекты бывают двух видов:
- 🧩 Собственные — созданы в расширении. Их имена начинаются с префикса, который задан в свойствах расширения (поле «Префикс имён»). Конфигуратор подставляет префикс автоматически при добавлении объекта.
- 🔗 Заимствованные — добавлены из основной конфигурации командой «Добавить в расширение» из контекстного меню объекта. Имя у них остаётся оригинальным, без префикса.
Заимствовать общий модуль нужно только в двух случаях: чтобы перехватить его методы (&Перед, &После, &Вместо, &ИзменениеИКонтроль) или чтобы дописать в него новые процедуры. Для простого вызова заимствование лишнее.
Расширения подключаются к базе через Конфигурация → Расширения конфигурации в конфигураторе или через Администрирование → Печатные формы, отчёты и обработки → Расширения в программах на БСП. Сам механизм появился в платформе 8.3.6.
Есть и обратная сторона. Основная конфигурация ничего не знает о расширении на этапе разработки. Её код не может напрямую обратиться к собственному модулю расширения — об этом отдельный раздел ниже.
Синтаксис вызова общего модуля из расширения
Обычный неглобальный модуль вызывается через точку. Возьмём типовой модуль БСП ОбщегоНазначения и вызовем его из собственного модуля расширения Расш1_ОбработкаЗаказов:
// Общий модуль расширения Расш1_ОбработкаЗаказов (Сервер)
Функция Расш1_КонтрагентЗаказа(Заказ) Экспорт
// Модуль основной конфигурации — без префикса
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Заказ, "Контрагент");
КонецФункции
Собственный модуль расширения вызывается по полному имени, с префиксом:
Контрагент = Расш1_ОбработкаЗаказов.Расш1_КонтрагентЗаказа(ДокументСсылка);
Префикс в имени экспортной процедуры не обязателен. Но если вы добавляете новые методы в заимствованный модуль, префикс в имени метода защищает от конфликта: разработчик конфигурации может завтра добавить метод с тем же именем, и после обновления расширение перестанет применяться.
Для глобального модуля (флаг «Глобальный») имя модуля не пишется. Экспортные методы вызываются как обычные процедуры:
// ГлобальныйМодуль конфигурации содержит Экспорт-функцию ТекущийПользовательИмя()
Имя = ТекущийПользовательИмя();
Сводка по вариантам:
| Что вызываем | Как пишем | Нужно заимствование |
|---|---|---|
| Модуль конфигурации | ИмяМодуля.Метод() | Нет |
| Глобальный модуль конфигурации | Метод() | Нет |
| Собственный модуль расширения | Расш1_Модуль.Метод() | Нет |
| Новый метод в заимствованном модуле | ИмяМодуля.Расш1_Метод() | Да |
| Модуль другого расширения | Напрямую нельзя | — |
Свойства общего модуля, от которых зависит доступ
Код расширения подчиняется тем же правилам компиляции, что и код конфигурации. Модуль виден только в тех контекстах, которые разрешены его флагами. Флаги смотрите в палитре свойств модуля в основной конфигурации.
| Флаг | Что даёт для вызова из расширения |
|---|---|
| Клиент (управляемое приложение) | Модуль доступен в клиентском коде форм и команд |
| Сервер | Модуль доступен в серверном коде: &НаСервере, модули объектов и менеджеров |
| Вызов сервера | Методы можно вызывать прямо с клиента, платформа сама делает серверный вызов |
| Внешнее соединение | Модуль доступен при подключении через COM |
| Привилегированный | Код модуля выполняется на сервере без проверки прав |
| Повторное использование | Результат функции кешируется на время вызова или сеанса |
| Глобальный | Методы вызываются без имени модуля |
Флаги заимствованного модуля задаёт основная конфигурация. Через расширение нельзя превратить серверный модуль в модуль с вызовом сервера или сделать его клиентским. Если нужного флага нет — создайте свой модуль в расширении с подходящими флагами и вызывайте модуль конфигурации из него. Такой модуль-переходник закрывает большинство проблем с контекстом.
Пример: в конфигурации есть серверный модуль РаботаСЦенами без флага «Вызов сервера». С клиента его не вызвать. Делаем в расширении модуль Расш1_ЦеныВызовСервера с флагами «Сервер» и «Вызов сервера»:
// Общий модуль расширения Расш1_ЦеныВызовСервера
// Флаги: Сервер, Вызов сервера
Функция Расш1_ЦенаНоменклатуры(Номенклатура, ВидЦены) Экспорт
Возврат РаботаСЦенами.ЦенаНоменклатуры(Номенклатура, ВидЦены, ТекущаяДатаСеанса());
КонецФункции
Имя и параметры метода ЦенаНоменклатуры здесь условные. Сверьте сигнатуру с вашей конфигурацией: откройте модуль и найдите метод через Ctrl+F.
Обращение к модулю с клиента и с сервера
Самая частая ошибка — вызов серверного модуля из клиентской процедуры формы. Разберём, что где работает в модуле формы расширения.
Код на клиенте
В процедуре с директивой &НаКлиенте доступны:
- ✅ модули с флагом «Клиент (управляемое приложение)»;
- ✅ модули с флагами «Сервер» + «Вызов сервера»;
- ❌ серверные модули без «Вызова сервера» — компилятор их не увидит.
Правильная схема: клиентская процедура вызывает серверную процедуру формы, а та — серверный модуль.
&НаКлиенте
Процедура Расш1_ЗаполнитьЦенуПослеВыбора(Команда)
Расш1_ЗаполнитьЦенуНаСервере();
КонецПроцедуры
&НаСервере
Процедура Расш1_ЗаполнитьЦенуНаСервере()
// Здесь доступен любой серверный модуль конфигурации
Объект.Цена = РаботаСЦенами.ЦенаНоменклатуры(Объект.Номенклатура, Объект.ВидЦены, ТекущаяДатаСеанса());
КонецПроцедуры
Код на сервере без контекста
Директива &НаСервереБезКонтекста экономит трафик: на сервер не передаются данные формы. Обращаться к общим серверным модулям из неё можно, но к реквизитам формы — нельзя. Всё нужное передавайте параметрами.
Модули объектов и менеджеров
Если вы заимствовали документ и пишете обработчик в его модуле объекта, код исполняется на сервере. Серверные модули конфигурации вызываются оттуда напрямую.
Каждый вызов с клиента на сервер — сетевой круг. Не вызывайте серверный метод в цикле по строкам таблицы на клиенте: соберите данные и сделайте один вызов.
Права доступа и безопасный режим расширения
Код расширения выполняется с правами текущего пользователя. Если общий модуль конфигурации читает справочник, на который у пользователя нет прав, вызов из расширения упадёт с ошибкой Нарушение прав доступа!. Это поведение платформы, расширение тут ни при чём.
Варианты решения:
- Роль в расширении. Добавьте в расширение собственную роль с правами на нужные объекты и назначьте её пользователям. В БСП — через профили групп доступа.
- Привилегированный модуль. Модуль с флагом «Привилегированный» выполняется на сервере без проверки прав. Подходит, если пользователь не должен получать права напрямую.
УстановитьПривилегированныйРежим(Истина)в серверном коде перед вызовом. Не забывайте выключать режим после нужной операции.
Отдельная история — безопасный режим. При подключении расширения в пользовательском режиме база может предложить включить для него безопасный режим и защиту от опасных действий. В безопасном режиме коду расширения запрещены операции с файлами на сервере, COM-объекты, внешние компоненты и часть системных функций. Если модуль конфигурации внутри делает что-то из этого и вызывается из расширения в безопасном режиме, получите ошибку о запрете операции.
Свойство «Безопасный режим» видно в карточке расширения в списке расширений. Снимать его стоит только для расширений, которым вы доверяете: код расширения получает те же возможности, что и код конфигурации.
Заимствование модуля и перехват методов
Когда задача — не просто вызвать метод, а изменить его поведение, нужен перехват. Шаги:
- В конфигураторе откройте дерево основной конфигурации.
- Правой кнопкой по общему модулю — «Добавить в расширение», выберите нужное расширение.
- Откройте заимствованный модуль в расширении. Он пустой — исходный код остаётся в конфигурации.
- Напишите процедуру с аннотацией и именем перехватываемого метода.
&После("ЗаполнитьДанныеПечати")
Процедура Расш1_ЗаполнитьДанныеПечати(ДанныеПечати) Экспорт
ДанныеПечати.Вставить("Расш1_Примечание", "Отгрузка со склада №2");
КонецПроцедуры
Какую аннотацию выбрать:
| Аннотация | Когда применять | Риск при обновлении |
|---|---|---|
&Перед | Подготовить данные до исходного метода | Низкий |
&После | Дополнить результат | Низкий |
&Вместо | Полностью заменить логику; ПродолжитьВызов() вызывает оригинал | Средний: изменения вендора в методе не дойдут |
&ИзменениеИКонтроль | Точечная правка внутри метода (#Вставка, #Удаление) | Высокий: при изменении исходного текста расширение перестаёт применяться |
&Вместо с ПродолжитьВызов() даёт управлять, вызывать ли оригинал вообще. Для функции ПродолжитьВызов() возвращает её результат, его можно изменить и вернуть дальше.
Другие точки встраивания
Не всегда нужно трогать общий модуль. В типовых конфигурациях на БСП есть модули-переопределения с именами вида ...Переопределяемый (например, ОбщегоНазначенияПереопределяемый). Они созданы как раз для доработок: заимствуете такой модуль и пишете код в его процедурах-заглушках через &После. Риск конфликта при обновлении здесь минимальный.
Другие варианты: подписки на события (собственные в расширении), регламентные задания расширения, обработчики событий заимствованных форм.
Как вызвать модуль расширения из основной конфигурации
Обратная задача встречается часто: код конфигурации должен вызвать процедуру расширения, но расширение может быть и не подключено. Прямой вызов Расш1_Модуль.Метод() в конфигурации не скомпилируется.
Рабочие приёмы:
- 🔁 Перехват. Заимствуйте нужный модуль конфигурации, повесьте
&Послена метод и уже из перехвата вызывайте свой модуль. Конфигурация ничего не знает о расширении, а код всё равно выполняется. - 🔍 Проверка наличия плюс вызов по имени. В конфигурациях на БСП есть
ОбщегоНазначения.ПодсистемаСуществует()иОбщегоНазначения.ОбщийМодуль("ИмяМодуля"). Второй возвращает модуль по строковому имени:
Если Метаданные.ОбщиеМодули.Найти("Расш1_ОбработкаЗаказов") <> Неопределено Тогда
Модуль = ОбщегоНазначения.ОбщийМодуль("Расш1_ОбработкаЗаказов");
Модуль.Расш1_ПослеПроведения(Ссылка);
КонецЕсли;
Проверка через Метаданные.ОбщиеМодули.Найти() работает и без БСП: если расширение подключено, его модули попадают в коллекцию метаданных сеанса.
Из одного расширения в собственный модуль другого расширения напрямую обратиться тоже нельзя: заимствовать объекты другого расширения не получится. Тот же приём с поиском в метаданных и вызовом по имени подходит и здесь.
Ошибки при вызове и отладка
Тексты ошибок почти всегда указывают на причину. Сопоставьте свой случай:
&НаКлиенте. Перенесите вызов в серверную процедуру формы или сделайте модуль-переходник в расширении.&ИзменениеИКонтроль. Откройте расширение в конфигураторе и выполните проверку применимости, поправьте перехваты.Как отлаживать вызовы
- 🐞 Точки останова ставятся в модулях расширения так же, как в конфигурации. Откройте расширение из окна
Конфигурация → Расширения конфигурации, затем нужный модуль, и запускайте отладку как обычно. - 🔎 В отладчике можно шагнуть внутрь вызова (
F11) и попасть в модуль основной конфигурации: оба кода исполняются в одном сеансе. - 🧪 Перед сохранением выполните синтаксический контроль модуля (
Ctrl+F7). Он ловит неизвестные модули и неэкспортные методы ещё до запуска. - 📝 Для трудноуловимых случаев оборачивайте вызов в
Попытка … Исключениеи пишите в журнал регистрации подробности черезПодробноеПредставлениеОшибки(ИнформацияОбОшибке()).
Попытка
Результат = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Контрагент");
Исключение
ЗаписьЖурналаРегистрации("Расш1.ВызовМодуля", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Не глушите исключение пустым блоком Исключение. Пользователь не увидит ошибку, а данные останутся заполненными наполовину.
После правки модулей расширения в клиент-серверной базе пользователям нужно перезапустить сеанс: изменения расширения применяются при новом входе.