Почему TurboMD в 1С 7.7 не видит переменные в контексте и как это исправить?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

Разработчики, давно работающие с платформой 1С:Предприятие 7.7, прекрасно знают, насколько сильно внешняя компонента TurboMD (созданная Александром Орефковым) упрощает жизнь. Возможность обновлять программный код модулей документов, справочников и отчетов "на лету", без монопольного выталкивания пользователей из базы данных — это бесценный инструмент отладки. Однако при активном использовании этого механизма можно столкнуться с крайне неприятной проблемой: при передаче контекста объекта в Глобальный модуль новые переменные, объявленные в динамически загруженном модуле, оказываются невидимы. При попытке обратиться к ним система выдает ошибку, а в худших случаях происходит непредсказуемый сдвиг атрибутов.

В этой статье мы подробно разберем, почему возникает такое поведение платформы, как устроен механизм компиляции контекста в 1С 7.7, и предложим несколько надежных практических способов обойти это ограничение во время разработки и отладки.

Выясняем техническую причину: почему контекст «не видит» переменные?

Для того чтобы понять природу проблемы, нам придется заглянуть под капот движка 1С 7.7. Когда пользователь открывает форму объекта (например, документа Счет), платформа выполняет статическую инициализацию этого объекта в памяти. В этот момент происходит два ключевых действия:

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

Компонента TurboMD работает как перехватчик компилятора. Когда вы изменяете модуль и сохраняете его во внешний файл, TurboMD подменяет p-код исполняемых процедур "на лету". Но она не может перестроить структуру контекста уже созданного в памяти объекта! Физическая таблица переменных в памяти остается старой.

Если вы добавили в текстовый файл модуля новую переменную:

Перем НоваяПеременная Экспорт;

а затем попытались передать контекст этой формы в глобальную функцию:


// В модуле формы:
глФункцияОбработки(Контекст);

и прочитать её в Глобальном модуле:


// В Глобальном модуле:
Функция глФункцияОбработки(Конт)
    Возврат Конт.НоваяПеременная; // Тут произойдет ошибка!
КонецФункции

то платформа 1С 7.7 выдаст ошибку о том, что поле объекта не обнаружено. Физически в структуре переданного объекта по адресу в памяти этой переменной просто не существует, так как объект был создан до того, как вы динамически обновили код через TurboMD.

Опасность старых версий: баг со «сдвигом атрибутов»

Проанализируем еще одну критическую проблему. В ранних версиях компоненты TurboMD (до версии 1.0.1.2 включительно) присутствовала очень неприятная ошибка. При попытке динамической загрузки модуля с измененным списком переменных происходил так называемый "сдвиг атрибутов".

Например, обращение к переменной Контекст.Контрагент могло внезапно вернуть значение реквизита Договор или вообще привести к аварийному завершению работы программы (скрытый Access Violation). Это происходило из-за того, что компонента некорректно сопоставляла смещения переменных в скомпилированном байт-коде и реальной таблице объектов.

Решение: Если вы до сих пор используете старую сборку, обязательно обновите TurboMD до версии 1.0.1.2 или выше, где этот баг был полностью устранен автором.

Как передать данные в контекст «на лету» без объявления новых переменных

Что же делать, если при отладке функционала вам жизненно необходимо передать дополнительное значение из модуля формы в глобальную функцию через Контекст, но объявлять новые переменные модуля нельзя? Рассмотрим три эффективных обходных пути.

Способ 1. Использование глобального буфера отладки

Поскольку мы не можем расширить локальный контекст формы, мы можем использовать глобальную переменную-коллекцию в качестве связующего звена. Для этого отлично подойдет объект СписокЗначений или ТаблицаЗначений.

Разберем по шагам реализацию этого механизма:

1. В самом начале Глобального модуля объявим экспортную переменную:

Перем глБуферОтладки Экспорт;

2. В процедуре ПриНачалеРаботыСистемы инициализируем наш буфер:


Процедура ПриНачалеРаботыСистемы()
    глБуферОтладки = СоздатьОбъект("СписокЗначений");
КонецПроцедуры

3. В модуле формы, который мы отлаживаем через TurboMD, вместо объявления новой переменной будем помещать значение в глобальный список, привязывая его к уникальному идентификатору или ссылке на саму форму (переданный контекст):


// Измененный код модуля формы (загружаемый через TurboMD)
Процедура ПередВызовомГлобальнойФункции()
    // Записываем нужное нам значение в глобальный буфер
    // В качестве ключа используем уникальное строковое представление контекста
    КлючКонтекста = ПолучитьСтрокуID(Контекст); 
    глБуферОтладки.Установить(КлючКонтекста, "НашеПередаваемоеЗначение");
    
    // Вызываем глобальную функцию
    глФункцияОбработки(Контекст);
КонецПроцедуры

4. В Глобальном модуле считываем значение из буфера по ключу контекста:


Функция глФункцияОбработки(Конт)
    КлючКонтекста = ПолучитьСтрокуID(Конт);
    ЗначениеПеременной = глБуферОтладки.Получить(КлючКонтекста);
    
    Если ПустоеЗначение(ЗначениеПеременной) = 0 Тогда
        Сообщить("Получено значение через буфер: " + ЗначениеПеременной);
    КонецЕсли;
    
    // Очищаем буфер после использования, чтобы избежать утечек памяти
    глБуферОтладки.УдалитьЗначение(КлючКонтекста);
КонецФункции

Способ 2. Использование динамических свойств через FormEx

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

Для записи произвольного значения в контекст формы можно использовать расширение объектов FormEx, что позволит прочитать добавленный атрибут в любом месте программы, куда был передан этот контекст.

Способ 3. Использование конструкции Шаблон()

В некоторых случаях для получения значений переменных контекста без прямого обращения к ним через точку можно использовать функцию платформы Шаблон(). Однако этот способ имеет ограничения при работе с локальными неэкспортными переменными и требует осторожности при динамической подмене кода.

Особенности работы с модулями Видов Расчета

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

В чем причина такого поведения?
Движок периодических расчетов 1С 7.7 работает по принципу агрессивного кэширования. При самом первом расчете платформа обращается к компилятору для создания исполняемого кода модуля Вида Расчета. В этот момент TurboMD успешно перехватывает компиляцию и подставляет ваш измененный код.

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

Конфликты с 1С++ и классом «Перехватчик»

Если вы активно используете объектно-ориентированное расширение 1С++, вам нужно быть крайне осторожными при совместной работе с TurboMD. Здесь кроются две основные опасности.

1. Порядок инициализации внешних компонент

Чтобы TurboMD корректно уживалась с другими библиотеками (1С++, FormEx, TurboBL), критически важно соблюдать правильную последовательность их загрузки в процедуре ПриНачалеРаботыСистемы Глобального модуля. Компонента TurboMD должна загружаться строго последней!

Рассмотрим правильный порядок инициализации:


Процедура ПриНачалеРаботыСистемы()
    // 1. Сначала загружаем базовое расширение 1С++
    ЗагрузитьВнешнююКомпоненту("1cpp.dll");
    
    // 2. Затем расширение работы с интерфейсом FormEx
    ЗагрузитьВнешнююКомпоненту("formex.dll");
    
    // 3. Загружаем оптимизатор TurboBL (если используется)
    ЗагрузитьВнешнююКомпоненту("turbobl.dll");
    
    // 4. И только в самом конце загружаем TurboMD!
    ЗагрузитьВнешнююКомпоненту("turbomd.dll");
КонецПроцедуры

Если нарушить эту цепочку и загрузить TurboMD раньше, она перехватит компилятор первой. В результате механизмы 1С++ не смогут корректно перехватывать локальные события контекста (например, предопределенные процедуры вроде ПриЗаписи), либо сама TurboMD перестанет динамически обновлять код модулей.

2. Изменение состава методов класса «Перехватчик»

Класс «Перехватчик» в 1С++ строит внутреннюю карту методов модуля при первом обращении к объекту. Если с помощью TurboMD вы измените количество процедур/функций в модуле, их порядок или сигнатуру (количество параметров), кэш Перехватчика не обновится.

Это гарантированно приведет к непредсказуемому поведению платформы вплоть до падения программы с ошибкой "Access Violation". При использовании TurboMD безопасно изменять только внутреннее наполнение (алгоритмы) существующих процедур, но ни в коем случае не их структуру и состав.

Использование альтернативы: директива #ЗагрузитьИзФайла

В качестве альтернативы TurboMD для некоторых критически важных участков кода (где часто меняются переменные контекста) разумно использовать штатную недокументированную директиву платформы 1С 7.7: #ЗагрузитьИзФайла.

Рассмотрим, как это работает на практике. Вместо написания кода непосредственно в теле модуля конфигурации, мы пишем там одну строчку:


// В модуле формы документа в Конфигураторе:
#ЗагрузитьИзФайла ИмяФайлаМодуля.txt

При открытии формы платформа будет физически считывать и компилировать код из внешнего текстового файла ИмяФайлаМодуля.txt.

Преимущества данного подхода:

Недостатки:

Проанализировав все особенности работы с контекстом в 1С 7.7, мы видим, что связка из правильного порядка загрузки компонент, использования глобального буфера отладки для передачи новых параметров и комбинирования TurboMD с директивой #ЗагрузитьИзФайла дает разработчику полный контроль над процессом динамической отладки любой сложности.

← На главную