Почему не срабатывают инструкции препроцессора при определении клиента и как правильно определить тип клиентского приложения?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Анализ проблемы: почему препроцессор пропустил блок кода

Посмотрим на пример кода, который попытался применить разработчик в клиентском общем модуле:


Функция ПолучитьРежимЗапуска() Экспорт
    РежимЗапуска = Неопределено;
    #Если ТонкийКлиентУправляемоеПриложение Тогда
        РежимЗапуска = "ТонкийКлиент";
    #ИначеЕсли ВебКлиент Тогда
        РежимЗапуска = "ВебКлиент";
    #ИначеЕсли ТолстыйКлиентУправляемоеПриложение Тогда
        РежимЗапуска = "ТолстыйКлиент";
    #КонецЕсли
    
    Возврат РежимЗапуска;
КонецФункции

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

  1. Опечатка в названии препроцессорного символа. В синтаксисе встроенного языка 1С не существует символа препроцессора ТонкийКлиентУправляемоеПриложение. Корректное имя инструкции — ТонкийКлиент. В то же время символ ТолстыйКлиентУправляемоеПриложение является валидным, поэтому в толстом клиенте ветка успешно компилировалась.
  2. Особенность работы препроцессора платформы 1С при опечатках. Обратим внимание на то, как устроен синтаксический контроль платформы: если в инструкции #Если указан неизвестный или ошибочно написанный идентификатор, платформа не выдает синтаксических ошибок и не генерирует исключений. Любой нераспознанный идентификатор просто приравнивается к значению Ложь. В результате в байт-код клиентского приложения данный фрагмент вообще не попадает.
  3. Разница между директивами компиляции и инструкциями препроцессора. Конструкции, начинающиеся с символа # (#Если, #Область и др.), являются инструкциями препроцессора. Они обрабатываются до компиляции модуля и управляют тем, какие строки текста попадут в итоговый байт-код конкретного контекста. В свою очередь, директивы компиляции начинаются с символа амперсанда & (например, &НаКлиенте, &НаСервере) и указывают компилятору назначение конкретной процедуры или функции внутри модулей форм или команд. В общих модулях директивы компиляции не применяются — контекст их исполнения задается исключительно свойствами самого модуля («Клиент (управляемое приложение)», «Сервер» и т. д.).

Способ 1. Исправление синтаксиса инструкций препроцессора

Если требуется разделить логику на этапе компиляции, исправим наименования условий в соответствии со стандартами платформы. Заменим ошибочный идентификатор на стандартный ТонкийКлиент.

Посмотрим на корректный вариант функции:


Функция ПолучитьРежимЗапуска() Экспорт
    РежимЗапуска = Неопределено;
    #Если ТонкийКлиент Тогда
        РежимЗапуска = "ТонкийКлиент";
    #ИначеЕсли ВебКлиент Тогда
        РежимЗапуска = "ВебКлиент";
    #ИначеЕсли ТолстыйКлиентУправляемоеПриложение Тогда
        РежимЗапуска = "ТолстыйКлиент";
    #КонецЕсли
    
    Возврат РежимЗапуска;
КонецФункции

Важное ограничение: проанализируем архитектурную особенность. Инструкции #Если вычисляются в момент компиляции кода контекстом выполнения. Если этот общий модуль будет вызван из серверного контекста (например, при инициализации формы в обработчике ПриСозданииНаСервере), компиляция произойдет на стороне сервера 1С:Предприятия. На сервере условия ТонкийКлиент и ВебКлиент вернут Ложь, независимо от того, какое клиентское приложение вызвало форму.

Способ 2. Использование штатного метода ТипКлиентскогоПриложения()

Начиная с версии платформы 8.3.20, разработчикам доступен метод глобального контекста ТипКлиентскогоПриложения(). Он возвращает системное значение перечисления ТипКлиентскогоПриложения и позволяет отказаться от препроцессорных конструкций в пользу стандартного ветвления Если ... Тогда.

Разберем пример динамического определения клиента:


Функция ПолучитьТипТекущегоКлиента() Экспорт
    ТипКлиента = ТипКлиентскогоПриложения();
    
    Если ТипКлиента = ТипКлиентскогоПриложения.ТонкийКлиент Тогда
        Возврат "ТонкийКлиент";
    ИначеЕсли ТипКлиента = ТипКлиентскогоПриложения.ВебКлиент Тогда
        Возврат "ВебКлиент";
    ИначеЕсли ТипКлиента = ТипКлиентскогоПриложения.ТолстыйКлиент Тогда
        Возврат "ТолстыйКлиент";
    ИначеЕсли ТипКлиента = ТипКлиентскогоПриложения.МобильныйКлиент Тогда
        Возврат "МобильныйКлиент";
    КонецЕсли;
    
    Возврат "ДругойКлиент";
КонецФункции

Такой подход обеспечивает читаемость кода, исключает ошибки компиляции и легко поддается отладке.

Способ 3. Проверка через объект СистемнаяИнформация

Если требуется отличить запуск в браузере (веб-клиент) от настольного приложения на более ранних версиях платформы, воспользуемся объектом СистемнаяИнформация.

Рассмотрим реализацию проверки:


Функция ЭтоВебКлиент() Экспорт
    Информация = Новый СистемнаяИнформация;
    Возврат Не ПустаяСтрока(Информация.ИнформацияПрограммыПросмотра);
КонецФункции

Свойство ИнформацияПрограммыПросмотра заполняется строкой User Agent браузера исключительно при работе под управлением веб-клиента. В тонком и толстом клиентах это свойство возвращает пустую строку.

Способ 4. Определение клиента на стороне сервера

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

Посмотрим на пример функции для серверного контекста:


Функция ПолучитьИмяКлиентскогоПриложенияНаСервере() Экспорт
    НомерТекущегоСеанса = НомерСоединенияИнформационнойБазы();
    Сеансы = ПолучитьСеансыИнформационнойБазы();
    
    Для Каждого ТекущийСеанс Из Сеансы Цикл
        Если ТекущийСеанс.НомерСеанса = НомерТекущегоСеанса Тогда
            Возврат ТекущийСеанс.ИмяПриложения;
        КонецЕсли;
    КонецЦикла;
    
    Возврат "";
КонецФункции

Свойство ИмяПриложения возвращает системный идентификатор среды запуска:

  1. 1CV8C — тонкий клиент;
  2. WebClient — веб-клиент;
  3. 1CV8 — толстый клиент.

В конфигурациях, использующих «Библиотеку стандартных подсистем» (БСП), для решения аналогичной задачи рекомендуется использовать готовую серверную функцию общего модуля — СистемнаяИнформацияКлиента(), которая инкапсулирует получение сведений о среде пользователя без необходимости вручную перебирать сеансы.

← На главную