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

· 9 мин

Содержание

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

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

Как расширение видит объекты основной конфигурации

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

В дереве расширения объекты бывают двух видов:

  • 🧩 Собственные — созданы в расширении. Их имена начинаются с префикса, который задан в свойствах расширения (поле «Префикс имён»). Конфигуратор подставляет префикс автоматически при добавлении объекта.
  • 🔗 Заимствованные — добавлены из основной конфигурации командой «Добавить в расширение» из контекстного меню объекта. Имя у них остаётся оригинальным, без префикса.

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

Расширения подключаются к базе через Конфигурация → Расширения конфигурации в конфигураторе или через Администрирование → Печатные формы, отчёты и обработки → Расширения в программах на БСП. Сам механизм появился в платформе 8.3.6.

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

Синтаксис вызова общего модуля из расширения

Обычный неглобальный модуль вызывается через точку. Возьмём типовой модуль БСП ОбщегоНазначения и вызовем его из собственного модуля расширения Расш1_ОбработкаЗаказов:

// Общий модуль расширения Расш1_ОбработкаЗаказов (Сервер)
Функция Расш1_КонтрагентЗаказа(Заказ) Экспорт
	
	// Модуль основной конфигурации — без префикса
	Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Заказ, "Контрагент");
	
КонецФункции

Собственный модуль расширения вызывается по полному имени, с префиксом:

Контрагент = Расш1_ОбработкаЗаказов.Расш1_КонтрагентЗаказа(ДокументСсылка);

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

Для глобального модуля (флаг «Глобальный») имя модуля не пишется. Экспортные методы вызываются как обычные процедуры:

// ГлобальныйМодуль конфигурации содержит Экспорт-функцию ТекущийПользовательИмя()
Имя = ТекущийПользовательИмя();

Сводка по вариантам:

Что вызываемКак пишемНужно заимствование
Модуль конфигурацииИмяМодуля.Метод()Нет
Глобальный модуль конфигурацииМетод()Нет
Собственный модуль расширенияРасш1_Модуль.Метод()Нет
Новый метод в заимствованном модулеИмяМодуля.Расш1_Метод()Да
Модуль другого расширенияНапрямую нельзя—

Свойства общего модуля, от которых зависит доступ

Код расширения подчиняется тем же правилам компиляции, что и код конфигурации. Модуль виден только в тех контекстах, которые разрешены его флагами. Флаги смотрите в палитре свойств модуля в основной конфигурации.

ФлагЧто даёт для вызова из расширения
Клиент (управляемое приложение)Модуль доступен в клиентском коде форм и команд
СерверМодуль доступен в серверном коде: &НаСервере, модули объектов и менеджеров
Вызов сервераМетоды можно вызывать прямо с клиента, платформа сама делает серверный вызов
Внешнее соединениеМодуль доступен при подключении через COM
ПривилегированныйКод модуля выполняется на сервере без проверки прав
Повторное использованиеРезультат функции кешируется на время вызова или сеанса
ГлобальныйМетоды вызываются без имени модуля

Флаги заимствованного модуля задаёт основная конфигурация. Через расширение нельзя превратить серверный модуль в модуль с вызовом сервера или сделать его клиентским. Если нужного флага нет — создайте свой модуль в расширении с подходящими флагами и вызывайте модуль конфигурации из него. Такой модуль-переходник закрывает большинство проблем с контекстом.

Пример: в конфигурации есть серверный модуль РаботаСЦенами без флага «Вызов сервера». С клиента его не вызвать. Делаем в расширении модуль Расш1_ЦеныВызовСервера с флагами «Сервер» и «Вызов сервера»:

// Общий модуль расширения Расш1_ЦеныВызовСервера
// Флаги: Сервер, Вызов сервера
Функция Расш1_ЦенаНоменклатуры(Номенклатура, ВидЦены) Экспорт
	
	Возврат РаботаСЦенами.ЦенаНоменклатуры(Номенклатура, ВидЦены, ТекущаяДатаСеанса());
	
КонецФункции

Имя и параметры метода ЦенаНоменклатуры здесь условные. Сверьте сигнатуру с вашей конфигурацией: откройте модуль и найдите метод через Ctrl+F.

Обращение к модулю с клиента и с сервера

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

Код на клиенте

В процедуре с директивой &НаКлиенте доступны:

  • ✅ модули с флагом «Клиент (управляемое приложение)»;
  • ✅ модули с флагами «Сервер» + «Вызов сервера»;
  • ❌ серверные модули без «Вызова сервера» — компилятор их не увидит.

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

&НаКлиенте
Процедура Расш1_ЗаполнитьЦенуПослеВыбора(Команда)
	Расш1_ЗаполнитьЦенуНаСервере();
КонецПроцедуры

&НаСервере
Процедура Расш1_ЗаполнитьЦенуНаСервере()
	// Здесь доступен любой серверный модуль конфигурации
	Объект.Цена = РаботаСЦенами.ЦенаНоменклатуры(Объект.Номенклатура, Объект.ВидЦены, ТекущаяДатаСеанса());
КонецПроцедуры

Код на сервере без контекста

Директива &НаСервереБезКонтекста экономит трафик: на сервер не передаются данные формы. Обращаться к общим серверным модулям из неё можно, но к реквизитам формы — нельзя. Всё нужное передавайте параметрами.

Модули объектов и менеджеров

Если вы заимствовали документ и пишете обработчик в его модуле объекта, код исполняется на сервере. Серверные модули конфигурации вызываются оттуда напрямую.

Каждый вызов с клиента на сервер — сетевой круг. Не вызывайте серверный метод в цикле по строкам таблицы на клиенте: соберите данные и сделайте один вызов.

Права доступа и безопасный режим расширения

Код расширения выполняется с правами текущего пользователя. Если общий модуль конфигурации читает справочник, на который у пользователя нет прав, вызов из расширения упадёт с ошибкой Нарушение прав доступа!. Это поведение платформы, расширение тут ни при чём.

Варианты решения:

  1. Роль в расширении. Добавьте в расширение собственную роль с правами на нужные объекты и назначьте её пользователям. В БСП — через профили групп доступа.
  2. Привилегированный модуль. Модуль с флагом «Привилегированный» выполняется на сервере без проверки прав. Подходит, если пользователь не должен получать права напрямую.
  3. УстановитьПривилегированныйРежим(Истина) в серверном коде перед вызовом. Не забывайте выключать режим после нужной операции.

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

Свойство «Безопасный режим» видно в карточке расширения в списке расширений. Снимать его стоит только для расширений, которым вы доверяете: код расширения получает те же возможности, что и код конфигурации.

Заимствование модуля и перехват методов

Когда задача — не просто вызвать метод, а изменить его поведение, нужен перехват. Шаги:

  1. В конфигураторе откройте дерево основной конфигурации.
  2. Правой кнопкой по общему модулю — «Добавить в расширение», выберите нужное расширение.
  3. Откройте заимствованный модуль в расширении. Он пустой — исходный код остаётся в конфигурации.
  4. Напишите процедуру с аннотацией и именем перехватываемого метода.
&После("ЗаполнитьДанныеПечати")
Процедура Расш1_ЗаполнитьДанныеПечати(ДанныеПечати) Экспорт
	ДанныеПечати.Вставить("Расш1_Примечание", "Отгрузка со склада №2");
КонецПроцедуры

Какую аннотацию выбрать:

АннотацияКогда применятьРиск при обновлении
&ПередПодготовить данные до исходного методаНизкий
&ПослеДополнить результатНизкий
&ВместоПолностью заменить логику; ПродолжитьВызов() вызывает оригиналСредний: изменения вендора в методе не дойдут
&ИзменениеИКонтрольТочечная правка внутри метода (#Вставка, #Удаление)Высокий: при изменении исходного текста расширение перестаёт применяться

&Вместо с ПродолжитьВызов() даёт управлять, вызывать ли оригинал вообще. Для функции ПродолжитьВызов() возвращает её результат, его можно изменить и вернуть дальше.

Другие точки встраивания

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

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

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

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

Рабочие приёмы:

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

Проверка через Метаданные.ОбщиеМодули.Найти() работает и без БСП: если расширение подключено, его модули попадают в коллекцию метаданных сеанса.

Из одного расширения в собственный модуль другого расширения напрямую обратиться тоже нельзя: заимствовать объекты другого расширения не получится. Тот же приём с поиском в метаданных и вызовом по имени подходит и здесь.

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

Тексты ошибок почти всегда указывают на причину. Сопоставьте свой случай:

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

Как отлаживать вызовы

  • 🐞 Точки останова ставятся в модулях расширения так же, как в конфигурации. Откройте расширение из окна Конфигурация → Расширения конфигурации, затем нужный модуль, и запускайте отладку как обычно.
  • 🔎 В отладчике можно шагнуть внутрь вызова (F11) и попасть в модуль основной конфигурации: оба кода исполняются в одном сеансе.
  • 🧪 Перед сохранением выполните синтаксический контроль модуля (Ctrl+F7). Он ловит неизвестные модули и неэкспортные методы ещё до запуска.
  • 📝 Для трудноуловимых случаев оборачивайте вызов в Попытка … Исключение и пишите в журнал регистрации подробности через ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()).
Попытка
	Результат = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Контрагент");
Исключение
	ЗаписьЖурналаРегистрации("Расш1.ВызовМодуля", УровеньЖурналаРегистрации.Ошибка, , ,
		ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
	ВызватьИсключение;
КонецПопытки;

Не глушите исключение пустым блоком Исключение. Пользователь не увидит ошибку, а данные останутся заполненными наполовину.

После правки модулей расширения в клиент-серверной базе пользователям нужно перезапустить сеанс: изменения расширения применяются при новом входе.

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

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