Коллеги, хочу предложить Вам небольшое исследование на предмет изменения функционала серверных контекстов. Речь пойдет о x64-версии 1С сервера приложений для ОС Windows.
Несколько слов о терминах.
Наверное, я не ошибусь, если скажу, что большинство объектов(в широком смысле этого слова), которые мы привыкли использовать на внутреннем языке платформы, в реальности представляют собой COM-объекты. Именно ими оперирует система. Такие объекты, как <Справочники>, <РезультатЗапроса>, <ДокументОбъект> и т.д, суть COM-объекты внутри движка. Даже числа и строки "оборачиваются" в COM-интерфейсы.
В данном изложении я буду использовать термин Контекст, подразумевая под ним набор интерфейсов подобных Com-объектов. Описание самих интерфейсов и их IID вы вряд ли найдете в MSDN и regedit'ом в реестре, поскольку это сугубо внутренние интерфейсы платформы. Я не говорю сейчас об IUnknown, IDispatch и прочих стандартных интерфейсах. Среди набора интерфейсов 'внутренних' COM-объектов нам будет интересен интерфейс "вызова через <.>". Мне он видится как некий аналог IDispatch. Я буду ссылаться на него как IContextExtImpBase.
Когда мы пишем, например, Справочники.<...>, именно в этот момент происходит обращение к методам интерфейса IContextExtImpBase. Хотя нет, если посмотреть на это более детально, то окажется, что объект 'Справочники', это свойство одного из глобальных контекстов (которых в 1С порядка 33), т.е. внутри обращение к 'Справочники' выглядит так <ГК(i)>.Справочники...И каждый из ГК поддерживает тот же IContextExtImpBase.
Как известно, любой COM-интерфейс имеет строго определенную последовательность методов в виртуальной таблице, поэтому можно попытаться выполнить подмену какого-либо методов интерфейса. Например, за вызовы методов 1С-объектов отвечает метод "IContextExtImpBase::call". Если его подменить на что-то своё, то все вызовы методов 1С-го объекта будут проходить через ваш метод. Причем, это коснется всего серверного рабочего процесса.
Моя цель была, попытаться минимальными усилиями, после перехвата, "всплыть" на уровень модуля 1С и уже тут продолжить обработку. Для экспериментов я использовал объект Запрос, как наиболее интересный в плане изменения функционала.Перехватывая его методы, можно, например, собирать статистику, ну или налету подменять текст запроса.(Не могу с уверенностью утверждать, но даже если конфигурация поставляется без текста модулей, текст запроса можно получить.)
Вариант реализации.
Я применил не совсем стандартный подход для решения данной задачи, каюсь. Основные инструменты, которые я использовал, это COM-объект DynamicWrapperX и кусок бинарного кода. (Исходник есть в тестовой базе. Я старался делать подробные комментарии.). Оба написаны на goAsm'е. Весь остальной код написан на стандартном языке 1С. Уверен, что существуют более изящные варианты реализации, но, в любом случае, это всего лишь пример того, как это можно сделать.
Теперь предлагаю посмотреть, что есть в тестовой конфигурации, и рассмотреть, как работает перехват.
- Серверный общий модуль 'DWX_Информатор'. Тут находятся служебные процедуры и функции для доступа к методам интерфейса IContextExtImpBase. Вообще говоря, сам модуль может использоваться независимо от данной разработки, но отдельно представляет скорее теоретический интерес. Возможно, его можно применить для UNIT-тестирования.
- Служебная обработка 'Перехват', которая забрасывает кусок бинарного кода на сервер и устанавливает/снимает перехват.
- Служебная обработка '_proxyInterceptor'. Основная обработка, в которой происходят перенаправления вызовов. Именно сюда (в модуль обработки) "всплывает" перехваченный вызов.
- Обработка 'ОбработчикПерехвата'. Содержит пользовательские обработчики методов перехваченного контекста.
Вот один из вариантов, как мог бы выглядеть перехваченный метод Запроса 'Выполнить':
Функция Событие_Запрос_Выполнить(Запрос) Экспорт
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "" + Запрос.Текст;
Сообщение.Сообщить();
Рез = Перехватчик.ВыполнитьОригинальноеСобытие();
Колво = ?(Рез = Неопределено, 0, Рез.Выбрать().Количество());
Сообщение.Текст = "Размер выборки:" + Колво;
Сообщение.Сообщить();
Возврат Рез;
КонецФункции
(Имена пользовательских процедур/функций строятся по принципу Событие_ТипКонтекста_ИмяМетода).
Как работает перехват?
Предлагаю для простоты считать, что мы имеем дело с объектом Запрос. Как сказано выше, перехват устанавливается глобально на весь рабочий процесс, в котором он вызван, а это значит, что все базы, которые подключены к этому процессу будут иметь дело с измененным методом "IContextExtImpBase::call" интерфейса. В момент вызова какого-либо метода объекта Запрос, выполняется поиск обработки с именем '_proxyInterceptor' в соответствующей базе. Если таковая существует, то выполняется поиск экспортного метода 'ВызватьПерехватчик'. Далее либо отрабатывает переопределенный метод из обработки 'ОбработчикПерехвата', либо выполняется вызов оригинального метода. Если обработки '_proxyInterceptor' не существует, то происходит вызов оригинального метода.
Важно (1)! Чтобы упростить реализацию, я предполагаю, что право на использование обработки '_proxyInterceptor' есть у каждого пользователя, во всех базах, где она присутствует. Это критично. Если это не так хотя бы для какого-то пользователя, в какой-либо базе, где есть эта обработка, то произойдет перезагрузка серверного процесса (если будет использован объект Запрос). Так же имеет смысл выдать право на использование обработки "ОбработчикПерехвата'. В случае отсутствия прав у пользователя, будет просто вызван стандартный метод.
Важно (2)! Всё вышесказанное, тестировалось на релизе 8.3.9.1818. Скорее всего, на релизах 8.3.9.xxxx тоже будет работать (требует проверки). Про поддержку других релизов имеет смысл говорить, когда будет понимание, нужна ли данная "конструкция" вообще.
Ограничения.
Что не удалось реализовать? "Нормальную" обработку 1С-х исключений. (Тут я привожу технические подробности, для понимания процесса.) В момент, когда тестовая база была практически готова, выяснилось, что при возникновении 1С-го исключения (например синтаксическая ошибка в тексте запроса) всё рушилось, т.е. рабочий процесс на сервере перегружался.
Поэтому, пришлось сделать рефакторинг 1С-го кода и обернуть критичные участки в Попытка...Исключение.
Далее. Если ошибка возникла при обработке оригинального события,
Рез = Перехватчик.ВыполнитьОригинальноеСобытие();
то устанавливается "флаг" ошибки и, уже в бинарном коде, повторно вызывается оригинальное событие, чтобы сгенерировать 1С-е исключение.
Если ошибка произошла внутри модуля обработчика, например, внутри функции,
Функция Событие_Запрос_Выполнить(Запрос) Экспорт
.
.
.
КонецФункции
то выводится просто сообщение об ошибке, выполнение при этом не прекращается. Пока мне не удалось это как-то обойти.
Важно (3)! Заметил такой нюанс: если запустить какое-нибудь длительное фоновое задание, например, перепроведение документов, и в нём выводить тексты Запросов, то это может привести к перезагрузке рабочего процесса, поскольку просто переполняется очередь сообщений, а объёмный текст и количество запросов этому способствуют. В этой связи я делаю проверку на тип клиента при выводе текста.
В заключении, хочу обратить внимание на ряд технических моментов, которые могут быть полезны при использовании DynamicWrapperX. Должен сказать, что с момента выхода DynamicWrapperX под x64, прошло более 2-х лет. И до последнего времени я и сам на x64-серверах ничего путного, кроме как Sleep, не использовал. Здесь, в тестовой базе, можно посмотреть другие варианты применения этого Com-объекта. Например, передача параметров через "слой" 'бинарный код-1С' и наоборот, получение функций-оберток, использование интерфейсов COM-объектов, прочее.
Резюмируя вышесказанное.
Чтобы посмотреть, как это работает, необходимо:
- Тестовый сервер с релизом 8.3.9.1818.
- DynamicWrapperX (http://dynwrapx.script-coding.com/dwx/pages/dynwrapx.php). Вам нужна версия для x64. Ее необходимо зарегистрировать на сервере.
- Установить прилагающуюся тестовую базу.
- Запустить тонкого клиента и в меню Сервис выбрать обработку Тест.
- Включить перехват (соответствующая кнопка).
- Протестировать перехват объекта Запрос. (Кнопка 'Тест запроса').
Для других баз:
- Скопировать в базу общий модуль DWX_Информатор (галка сервер).
- копировать 2 обработки _proxyInterceptor и ОбработчикПерехвата. Выдать права на использование обработок всем пользователям.
- В тестовой базе включить перехват.
- Если обе базы (тестовая и данная) подцеплены к одному рабочему процессу, то в окне сообщений можно увидеть результат перехвата.