При разработке расширений для 1С многие из нас сталкиваются с ситуацией, когда глобальная переменная, казалось бы, правильно объявленная в одном модуле расширения, оказывается недоступной в другом. Особенно часто этот вопрос возникает, когда речь идет о взаимодействии между МодулемУправляемогоПриложения и МодулемМенеджера объекта. Давайте вместе разберем эту проблему и выясним ее причину, предварительно проведя анализ расширений на наличие ошибок, а также рассмотрим эффективные способы передачи данных.
Для того чтобы понять, почему глобальная переменная не видна, нам необходимо глубже погрузиться в архитектуру платформы 1С:Предприятие, а именно в разделение кода на клиентский и серверный контексты. Это фундаментальное понятие, определяющее, где и как будет выполняться ваш код.
Модуль управляемого приложения
Этот модуль компилируется и выполняется на клиенте. Он предназначен для обработки событий, связанных с сеансом работы пользователя, таких как ПриНачалеРаботыСистемы или ПриЗавершенииРаботыСистемы. В этом модуле мы не указываем директивы компиляции (например, &НаКлиенте или &НаСервере), поскольку он целиком клиентский.
Куда доступны экспортируемые переменные? Экспортируемые переменные, процедуры и функции, определенные в МодулеУправляемогоПриложения расширения, будут доступны только в других клиентских контекстах. Рассмотрим их подробнее:
Важно отметить, что ни один из этих контекстов не является серверным.
Модуль менеджера объекта
В отличие от модуля управляемого приложения, МодульМенеджера любого объекта конфигурации (например, справочника, документа, отчета) компилируется и выполняется на сервере. Его основное назначение — описывать методы, которые относятся не к конкретному экземпляру объекта базы данных, а к объекту конфигурации в целом (например, методы получения списка, поиска, проверки уникальности).
Проанализируем его особенности:
МодульМенеджера может содержать только описание процедур и функций. Объявление переменных и тело модуля (код, который выполняется при загрузке модуля) в нем не поддерживаются.&НаКлиенте), так как он всегда серверный. Если нам нужно выполнить код в определенном серверном контексте, мы можем использовать директивы, как показано в примере ниже, но это лишь уточняет серверный контекст, а не меняет его на клиентский:
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
// Здесь может быть ваш серверный код
#КонецЕсли;
МодуляМенеджера доступны глобальный контекст и экспортируемые функции и процедуры общих модулей, скомпилированных на сервере.Вывод: Теперь нам становится очевидно, почему переменная, объявленная в клиентском МодулеУправляемогоПриложения расширения, не может быть видна в серверном МодулеМенеджера. Это разные пространства выполнения, как будто два разных компьютера, которые не имеют прямого доступа к памяти друг друга.
Помимо разделения на клиентский и серверный контексты, существует также разделение пространств имен между основной конфигурацией и расширением. Хотя расширение и "встраивается" в основную конфигурацию, оно все же существует как отдельный слой. Это означает, что:
Поскольку прямое использование "глобальных" переменных модуля приложения расширения в серверных модулях невозможно, нам нужно рассмотреть другие, более подходящие механизмы платформы для обмена данными между различными контекстами и модулями. Разберем по шагам несколько эффективных подходов, используя инструменты разработчика для тонкого клиента для проверки корректности передаваемых данных.
Передача параметров через процедуры и функции
Это самый очевидный, надежный и рекомендуемый способ передачи данных. Когда вам нужно использовать данные из клиентского контекста на сервере (или наоборот), просто передайте их как параметры в вызов серверной (или клиентской) процедуры или функции.
Пример:
// На клиенте (например, в модуле формы или МодулеУправляемогоПриложения расширения)
&НаКлиенте
Процедура МояКлиентскаяПроцедура()
МояПеременная = "Значение из клиента";
РезультатСервера = МойОбщийМодульРасширения.ВыполнитьНаСервере(МояПеременная);
Сообщить("Результат с сервера: " + РезультатСервера);
КонецПроцедуры;
// В ОбщемМодулеРасширения (свойство "Сервер")
&НаСервере
Функция ВыполнитьНаСервере(ПараметрСКлиента) Экспорт
// Здесь мы можем использовать ПараметрСКлиента
Возврат "Сервер обработал: " + ПараметрСКлиента;
КонецФункции;
Параметры сеанса
Если данные должны быть доступны на протяжении всего сеанса работы пользователя и не привязаны к конкретной форме или модулю, мы можем использовать параметры сеанса. Они доступны как на клиенте, так и на сервере.
Ограничения: Параметры сеанса могут хранить не все типы значений. В основном это примитивные типы, ссылки на объекты базы данных и некоторые стандартные объекты платформы. Для сложных структур данных или больших объемов информации этот способ может быть неоптимальным.
Пример:
// В МодулеСеанса (или в МодулеУправляемогоПриложения при начале сеанса)
// Для расширения: нужно создать или расширить существующий параметр сеанса
// и установить его значение.
// Допустим, у нас есть параметр сеанса "МойПараметрСеанса" в расширении.
// Установка значения (например, на клиенте через серверный вызов)
&НаСервере
Процедура УстановитьМойПараметрСеансаНаСервере(Значение)
ПараметрыСеанса.МойПараметрСеанса = Значение;
КонецПроцедуры;
// Получение значения (доступно как на клиенте, так и на сервере)
// На клиенте:
&НаКлиенте
Процедура ПолучитьПараметрСеансаНаКлиенте()
Сообщить("Значение параметра сеанса: " + ПараметрыСеанса.МойПараметрСеанса);
КонецПроцедуры;
// На сервере:
&НаСервере
Процедура ПолучитьПараметрСеансаНаСервере()
Сообщить("Значение параметра сеанса: " + ПараметрыСеанса.МойПараметрСеанса);
КонецПроцедуры;
Временное хранилище
Для передачи больших объемов данных, двоичных данных или файлов между клиентом и сервером (или наоборот) используйте временное хранилище. Это позволяет избежать передачи данных через параметры процедур, что может быть неэффективно для больших объемов.
Механизм: Мы помещаем данные в хранилище на одной стороне и получаем уникальный адрес, который затем передаем на другую сторону. По этому адресу данные можно извлечь.
Пример:
// На клиенте
&НаКлиенте
Процедура ПередатьФайлНаСервер()
ИмяФайла = "C:\temp\MyFile.txt";
ДвоичныеДанные = Новый ДвоичныеДанные(ИмяФайла);
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
// Передаем адрес на сервер
МойОбщийМодульРасширения.ОбработатьФайлНаСервере(АдресХранилища);
КонецПроцедуры;
// На сервере (в ОбщемМодулеРасширения)
&НаСервере
Процедура ОбработатьФайлНаСервере(АдресХранилища) Экспорт
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ДвоичныеДанные <> Неопределено Тогда
// Обрабатываем двоичные данные
Текст = Новый ТекстовыйДокумент();
Текст.УстановитьДвоичныеДанные(ДвоичныеДанные);
Сообщить("Содержимое файла на сервере: " + Текст.ПолучитьТекст());
КонецЕсли;
КонецПроцедуры;
Переменные управляемого или обычного приложения (только на клиенте)
Если вам нужны глобальные переменные, доступные только в клиентском контексте (например, из разных клиентских форм), вы можете использовать переменные, определенные в МодулеУправляемогоПриложения расширения. Как мы уже выяснили, они будут доступны в других клиентских модулях и формах расширения.
Пример:
// В МодулеУправляемогоПриложения расширения
Перем МояКлиентскаяГлобальнаяПеременная Экспорт;
Процедура ПриНачалеРаботыСистемы()
МояКлиентскаяГлобальнаяПеременная = "Значение установлено при старте";
КонецПроцедуры;
// В модуле клиентской формы расширения
&НаКлиенте
Процедура ПриОткрытии()
Сообщить("Переменная из МодуляУправляемогоПриложения: " + Модули.МодульУправляемогоПриложенияРасширения.МояКлиентскаяГлобальнаяПеременная);
КонецПроцедуры;
Обратите внимание, что для обращения к экспортируемой переменной из другого модуля расширения может потребоваться указание полного пути к модулю. Сэкономить время при частых запусках и проверках поможет помощник отладки (мини Vanessa).
Общие модули расширения
В расширении мы можем создавать собственные общие модули. Это очень мощный инструмент для организации кода.
Используя общие модули с правильными директивами компиляции, мы можем легко обмениваться данными между клиентским и серверным кодом, вызывая процедуры и функции с нужными параметрами.
Таким образом, понимание контекстов выполнения и применение подходящих механизмов передачи данных вместо попыток использования "глобальных" переменных там, где это архитектурно невозможно, позволит нам создавать надежные и эффективные решения в расширениях 1С.