При разработке и доработке конфигураций 1С:Предприятие, особенно при использовании расширений, мы можем столкнуться с ситуациями, когда код, прекрасно работающий в одном клиентском приложении, внезапно выдает ошибку "Переменная не определена" в другом.
Рассмотрим типовую ситуацию: в модуле менеджера расширения мы пытаемся вызвать функцию из общего модуля основной конфигурации, например, УправлениеПечатью.МакетПечатнойФормы(). В тонком клиенте все работает корректно, а в толстом клиенте возникает ошибка: "Переменная не определена УправлениеПечатью". Давайте проанализируем, почему это происходит, и как мы можем решить эту проблему.
Ошибка "Переменная не определена" для общего модуля УправлениеПечатью (или любого другого общего модуля, содержащего серверный код) в толстом клиенте часто возникает из-за различий в контексте выполнения кода между тонким и толстым клиентами, а также между различными режимами работы толстого клиента (обычное приложение и управляемое приложение).
Общие модули в 1С могут быть доступны как на сервере, так и на клиенте. Однако их доступность для компиляции и выполнения на клиенте определяется их свойствами и содержанием. Модуль УправлениеПечатью, как правило, содержит функции, которые должны выполняться на сервере, поскольку они часто работают с данными информационной базы, метаданными или требуют доступа к файловой системе сервера для работы с макетами.
УправлениеПечатью будет доступен в серверном контексте. Поэтому в тонком клиенте такая ошибка не возникает.
УправлениеПечатью в клиентском коде модуля менеджера расширения без явного указания, что этот вызов должен происходить на сервере, платформа попытается найти и скомпилировать модуль УправлениеПечатью в клиентском контексте. Поскольку этот модуль (или его экспортные функции) не предназначен для клиентской компиляции и выполнения, возникает ошибка "Переменная не определена". В режиме управляемого приложения толстый клиент ведет себя подобно тонкому, автоматически перенаправляя вызовы серверных функций, но в контексте модуля менеджера расширения это все равно может быть критично.
Модули менеджеров расширений могут иметь свои особенности, и их код по умолчанию может быть интерпретирован как клиентский, если не указано иное. Даже если модуль менеджера базовой конфигурации содержит правильные директивы препроцессора, это не означает, что такие же директивы автоматически применяются к модулю менеджера расширения. Нам необходимо явно указать контекст выполнения для кода в модуле менеджера расширения, который обращается к серверным функциям.
Для решения проблемы, когда код в модуле менеджера расширения в толстом клиенте не видит общий модуль УправлениеПечатью, мы можем применить два основных подхода. Оба подхода направлены на то, чтобы принудительно выполнять соответствующий участок кода в серверном контексте.
Самый надежный способ — это явно указать платформе, в каком контексте должен компилироваться и выполняться код, обращающийся к серверным модулям. Для этого мы используем директивы препроцессора #Если Сервер или более специфичные для различных клиентов.
Рассмотрим ситуацию: в нашем модуле менеджера расширения есть код, вызывающий функцию УправлениеПечатью.МакетПечатнойФормы(). Подобные вызовы часто требуются, когда необходимо выполнить заполнение Word-макетов при помощи БСП.
Исходный код (вызывающий ошибку), который часто встречается, когда используется шаблон ВПФ для вывода в Word:
// Модуль менеджера расширения
Процедура ПриНачалеРаботыСистемы()
// Какой-то код...
Макет = УправлениеПечатью.МакетПечатнойФормы("ОбщийМакет.ПФ_MXL_ПФ"); // Здесь возникает ошибка
// Какой-то еще код...
КонецПроцедуры
Чтобы избежать ошибки, нам нужно заключить вызов серверной функции в блок директив препроцессора, который разрешает компиляцию и выполнение этого кода только на сервере или в тех клиентских контекстах, где серверные вызовы возможны. Часто в типовых конфигурациях для модулей менеджеров используется следующая комбинация директив:
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
// Код, который должен выполняться на сервере или в специфических клиентских режимах
#КонецЕсли
Эта конструкция означает, что заключенный в нее код будет компилироваться и выполняться, если контекст текущего приложения является серверным, или если это толстый клиент в режиме обычного приложения, или если это внешнее соединение. Именно режим "ТолстыйКлиентОбычноеПриложение" является ключевым в нашей ситуации, поскольку он позволяет толстому клиенту понять, что этот участок кода нужно обрабатывать как серверный или доступный для выполнения в этом режиме.
Решение с использованием директив препроцессора:
// Модуль менеджера расширения
Процедура ПриНачалеРаботыСистемы()
// Какой-то код...
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
Макет = УправлениеПечатью.МакетПечатнойФормы("ОбщийМакет.ПФ_MXL_ПФ"); // Ошибка ушла!
#КонецЕсли
// Какой-то еще код...
КонецПроцедуры
Применив эти директивы в модуле менеджера нашего расширения, мы явно указываем платформе, что вызов УправлениеПечатью.МакетПечатнойФормы() должен происходить в серверном контексте, даже когда расширение работает в толстом клиенте. Таким образом, модуль УправлениеПечатью будет корректно распознан и его функции будут вызваны.
Второй способ заключается в том, чтобы явно пометить всю процедуру или функцию в модуле менеджера расширения, которая вызывает серверный код, как серверную.
Если код с вызовом УправлениеПечатью.МакетПечатнойФормы() находится внутри отдельной экспортной или внутренней процедуры/функции в модуле менеджера расширения, мы можем просто добавить к ней атрибут &НаСервере.
Пример использования атрибута &НаСервере:
// Модуль менеджера расширения
// ... какой-то клиентский код ...
Процедура ПриНачалеРаботыСистемы()
// Какой-то клиентский код...
ВыполнитьПечатьНаСервере();
// Какой-то еще клиентский код...
КонецПроцедуры
&НаСервере
Процедура ВыполнитьПечатьНаСервере()
Макет = УправлениеПечатью.МакетПечатнойФормы("ОбщийМакет.ПФ_MXL_ПФ"); // Ошибка ушла!
// Дополнительная логика работы с макетом...
КонецПроцедуры
В этом случае, когда процедура ВыполнитьПечатьНаСервере() вызывается из клиентского контекста (например, из ПриНачалеРаботыСистемы()), платформа 1С автоматически организует вызов этой процедуры на сервере. В серверном контексте модуль УправлениеПечатью гарантированно доступен, и ошибка "Переменная не определена" не возникнет.
Этот подход особенно удобен, когда только часть логики в модуле менеджера требует серверного выполнения, а остальной код может работать на клиенте. Мы можем выделить серверную логику в отдельные процедуры или функции и пометить их атрибутом &НаСервере.
УправлениеПечатью в расширение. Проблема заключается не в отсутствии самого модуля, а в недоступности его в конкретном контексте компиляции и выполнения кода из расширения. Заимствование общих модулей в расширение имеет смысл только в том случае, если мы хотим изменить их поведение, добавить свои экспортные функции или процедуры, или переопределить существующие.
УправлениеПечатью: Как правило, сам общий модуль УправлениеПечатью в основной конфигурации уже имеет правильные свойства и директивы препроцессора внутри себя, обеспечивающие его доступность на сервере. Поэтому проблема кроется именно в модуле менеджера *расширения*, который по умолчанию может быть интерпретирован как клиентский.
Таким образом, если мы столкнулись с ошибкой "Переменная не определена УправлениеПечатью" (или другим серверным общим модулем) в модуле менеджера расширения при работе в толстом клиенте, нам необходимо обеспечить выполнение кода, обращающегося к этому модулю, в серверном контексте. Мы можем сделать это одним из двух способов:
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда ... #КонецЕсли.&НаСервере.Оба метода эффективно решают проблему, позволяя нам корректно использовать серверные общие модули в расширениях, обеспечивая стабильную работу приложения во всех клиентских режимах.