При разработке решений на платформе 1С:Предприятие 8.3 с использованием управляемых форм, разработчики часто сталкиваются с ошибкой "Функция не определена" для методов, которые, казалось бы, должны быть доступны. Одна из таких ситуаций возникает, когда мы пытаемся использовать функцию СтрНайтиПоРегулярномуВыражению в модуле формы обработки и получаем ошибку: "Функция 'СтрНайтиПоРегулярномуВыражению' не определена [Тонкий клиент, Web-клиент]". Это происходит даже в том случае, если в настройках запуска конфигурации у нас указан "Толстый клиент" (версия платформы 8.3.27 и выше), и по описанию эта функция доступна на сервере и в толстом клиенте. Давайте подробно разберем, почему возникает эта проблема и как ее корректно решить.
Основная причина этой ошибки кроется в клиент-серверной архитектуре управляемых форм. Даже если мы запускаем 1С в режиме толстого клиента, код в модулях форм управляемого приложения может выполняться как на клиенте, так и на сервере. Функция СтрНайтиПоРегулярномуВыражению доступна только на сервере, толстом клиенте (в контексте именно серверной части толстого клиента, а не клиентской части управляемых форм), внешнем соединении и мобильном приложении (сервере), но она недоступна в контексте тонкого или веб-клиента. Это означает, что, несмотря на настройку запуска "Толстый клиент", часть нашего кода обработки попыталась выполнить эту функцию в клиентском контексте, где она попросту отсутствует.
Для корректного использования функции СтрНайтиПоРегулярномуВыражению нам необходимо обеспечить ее вызов именно на сервере. Рассмотрим подробнее, как это достигается с помощью директив компиляции и правильной организации кода.
В управляемых формах 1С код разделяется на две основные части: клиентскую и серверную. Клиентский код выполняется на компьютере пользователя (в тонком клиенте, веб-клиенте или даже в клиентской части толстого клиента), а серверный код — на сервере 1С:Предприятия или на том же компьютере, если это файловый вариант базы, но в отдельном процессе, имитирующем работу сервера. Функции, работающие с файловой системой сервера, базой данных, или требующие больших вычислений, как правило, доступны только на сервере. К таким функциям относится и СтрНайтиПоРегулярномуВыражению.
Для того чтобы явно указать, в каком контексте должна выполняться та или иная процедура или функция в модуле формы управляемого приложения, мы используем специальные директивы компиляции. Эти директивы определяют, будет ли код выполняться на клиенте или на сервере. Давайте разберем основные из них:
&НаСервере: Эта директива указывает, что процедура или функция должна быть выполнена на сервере. При этом, если процедура или функция находится в модуле формы, ей будет доступен контекст формы (т.е. реквизиты формы, элементы управления и их значения будут переданы на сервер). После выполнения серверного кода управление возвращается на клиент.Например, если нам нужно получить данные из реквизита формы, обработать их на сервере с помощью СтрНайтиПоРегулярномуВыражению, а затем обновить другой реквизит формы, мы можем использовать следующую структуру:
&НаКлиенте
Процедура КомандаОбработатьНаСервере(Команда)
// Вызываем серверную процедуру
ОбработатьТекстНаСервере();
КонецПроцедуры
&НаСервере
Процедура ОбработатьТекстНаСервере()
// Здесь мы имеем доступ к реквизитам формы, например, к РеквизитТекста
Если Не ЗначениеЗаполнено(РеквизитТекста) Тогда
Возврат;
КонецЕсли;
// Используем функцию СтрНайтиПоРегулярномуВыражению, которая доступна на сервере
НайденныеСовпадения = СтрНайтиПоРегулярномуВыражению(РеквизитТекста, "(\d+)");
// Обновляем реквизит формы, который будет отображен на клиенте
РеквизитРезультата = "Найдено: " + НайденныеСовпадения.Количество();
КонецПроцедуры
&НаСервереБезКонтекста: Эта директива также указывает, что процедура или функция должна быть выполнена на сервере. Однако, в отличие от &НаСервере, ей не будет доступен контекст формы. Это означает, что реквизиты формы не будут автоматически переданы на сервер. Такой подход является более производительным, так как не происходит передачи контекста формы между клиентом и сервером. Мы передаем на сервер только те параметры, которые явно указаны в сигнатуре процедуры/функции.Мы используем эту директиву, когда серверной процедуре не нужен прямой доступ к реквизитам формы, а достаточно только переданных параметров. Например:
&НаКлиенте
Процедура КомандаОбработатьБезКонтекста(Команда)
// Получаем значение с формы на клиенте
ТекстДляОбработки = РеквизитТекста;
// Вызываем серверную процедуру, передавая только необходимый параметр
РезультатОбработки = ОбработатьТекстНаСервереБезКонтекста(ТекстДляОбработки);
// Обновляем реквизит формы на клиенте
РеквизитРезультата = РезультатОбработки;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОбработатьТекстНаСервереБезКонтекста(ИсходныйТекст)
Если Не ЗначениеЗаполнено(ИсходныйТекст) Тогда
Возврат "";
КонецЕсли;
// Используем функцию СтрНайтиПоРегулярномуВыражению
НайденныеСовпадения = СтрНайтиПоРегулярномуВыражению(ИсходныйТекст, "(\d+)");
Возврат "Найдено: " + НайденныеСовпадения.Количество();
КонецФункции
Важно: Если для процедуры или функции в модуле формы не указана ни одна из этих директив, по умолчанию она будет выполняться на сервере с контекстом формы (т.е. эквивалентно &НаСервере).
&НаКлиенте: Эта директива указывает, что процедура или функция должна быть выполнена на клиенте. Код в таких процедурах не может обращаться к серверным функциям напрямую.Например:
&НаКлиенте
Процедура ПриОткрытии()
Сообщить("Форма открыта на клиенте.");
КонецПроцедуры
Мы видим, что из клиентской процедуры (отмеченной &НаКлиенте) можно вызвать серверную процедуру (отмеченную &НаСервере или &НаСервереБезКонтекста). При таком вызове происходит клиент-серверный вызов: управление передается на сервер, серверный код выполняется, и после его завершения управление возвращается обратно на клиент. Это позволяет нам выполнять необходимые серверные операции, включая использование функции СтрНайтиПоРегулярномуВыражению, а затем продолжать работу на клиенте.
Стоит помнить, что серверные процедуры не могут напрямую вызывать клиентские процедуры. Если серверному коду нужно выполнить какие-то действия на клиенте (например, обновить элементы формы, показать сообщение пользователю), он должен вернуть данные на клиент, и уже клиентская часть кода должна их обработать.
Для более структурированного и поддерживаемого кода мы часто выносим общую логику в общие модули. Если функция СтрНайтиПоРегулярномуВыражению используется в таком модуле, необходимо правильно настроить свойства этого общего модуля:
Мы рекомендуем разделять клиентскую и серверную логику на отдельные общие модули. Например, можно создать МодульКлиент с флагом "Клиент" и МодульСервер с флагами "Сервер" и "Вызов сервера". Такой подход помогает избежать проблем с надежным определением контекста выполнения и делает код более читаемым. Избегайте использования инструкций препроцессора (таких как #Если Клиент или #Если Сервер) для проверки контекста внутри одного общего модуля, особенно если этот модуль используется и на клиенте, и на сервере, так как это может привести к сложностям в отладке и неожиданному поведению. Для автоматической проверки корректности вызовов экспортных методов общих модулей можно использовать конфигурацию для анализа ошибок в программном коде.
Рассмотрим пример использования общего модуля:
// ОбщийМодульСервер.bsl (Свойства: Сервер = Истина, ВызовСервера = Истина)
Функция НайтиЧислаВТекстеНаСервере(Текст) Экспорт
Если Не ЗначениеЗаполнено(Текст) Тогда
Возврат 0;
КонецЕсли;
// СтрНайтиПоРегулярномуВыражению доступна здесь
НайденныеЧисла = СтрНайтиПоРегулярномуВыражению(Текст, "\d+");
Возврат НайденныеЧисла.Количество();
КонецФункции
// МодульФормыОбработки.bsl
&НаКлиенте
Процедура КомандаИспользоватьОбщийМодуль(Команда)
ТекстИзПоля = Элементы.ПолеВводаТекста.Значение;
Количество = ОбщийМодульСервер.НайтиЧислаВТекстеНаСервере(ТекстИзПоля);
Сообщить("Найдено чисел: " + Количество);
КонецПроцедуры
В ходе обсуждения часто всплывает вопрос о директивах типа #Если ТолстыйКлиент Тогда (как в сообщении 2). Важно понимать разницу между этими инструкциями препроцессора и директивами компиляции (&НаСервере, &НаКлиенте). Инструкции препроцессора (например, #Если Клиент, #Если Сервер, #Если ТолстыйКлиент, #Если ВебКлиент) определяют, какая часть кода будет компилироваться для данного типа клиента. Они фильтруют код на этапе компиляции.
Например:
#Если ТолстыйКлиент Тогда
// Этот код будет скомпилирован и доступен только при работе в толстом клиенте
Сообщить("Это сообщение только для толстого клиента");
#КонецЕсли
Однако, даже если код скомпилирован для толстого клиента, это не означает, что он автоматически будет выполняться в серверном контексте в управляемой форме. Если мы находимся в модуле формы управляемого приложения, то &НаСервере или &НаКлиенте определяют контекст выполнения процедуры/функции. Для корректной работы с серверной функцией в управляемой форме, приоритетнее использовать именно директиву &НаСервере, чтобы явно указать, что вызов должен произойти на сервере, независимо от типа клиента (тонкий, веб или толстый), в котором запущено управляемое приложение.
СтрНайтиПоРегулярномуВыражению и СтрНайтиВсеПоРегулярномуВыражению появились в платформе 1С:Предприятие версии 8.3.23 и выше. Мы должны убедиться, что наша версия платформы (в данном случае 8.3.27) поддерживает эту функциональность на сервере.Таким образом, для успешного использования функции СтрНайтиПоРегулярномуВыражению в управляемых формах необходимо всегда явно указывать, что ее вызов должен происходить на сервере, используя директивы компиляции &НаСервере или &НаСервереБезКонтекста, либо вызывая соответствующую серверную функцию из общего модуля.