Перехват методов серверных (x64) контекстов с использованием DynamicWrapperX

Публикация № 601153

Программирование - Инструментарий

DynamicWrapperX x64 goAsm перехват ассемблер

31
Применение COM-объекта DynamicWrapperX на x64-сервере приложений.

Коллеги, хочу предложить Вам небольшое исследование на предмет изменения функционала серверных контекстов. Речь пойдет о 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-объектов, прочее.

Резюмируя вышесказанное.

Чтобы посмотреть, как это работает, необходимо:

  1. Тестовый сервер с релизом 8.3.9.1818.
  2. DynamicWrapperX (http://dynwrapx.script-coding.com/dwx/pages/dynwrapx.php). Вам нужна версия для x64. Ее необходимо зарегистрировать на сервере.
  3. Установить прилагающуюся тестовую базу.
  4. Запустить тонкого клиента и в меню Сервис выбрать обработку Тест.
  5. Включить перехват (соответствующая кнопка).
  6. Протестировать перехват объекта Запрос. (Кнопка 'Тест запроса').

Для других баз:

  1. Скопировать в базу общий модуль DWX_Информатор (галка сервер).
  2. копировать 2 обработки _proxyInterceptor и ОбработчикПерехвата. Выдать права на использование обработок всем пользователям.
  3. В тестовой базе включить перехват.
  4. Если обе базы (тестовая и данная) подцеплены к одному рабочему процессу, то в окне сообщений можно увидеть результат перехвата.
31

Скачать файлы

Наименование Файл Версия Размер
Тестовая база
.dt 59,82Kb
23.03.17
7
.dt 1.0 59,82Kb 7 Скачать

См. также

Специальные предложения

Комментарии
Избранное Подписка Сортировка: Древо
1. bulpi 143 24.03.17 13:35 Сейчас в теме
Автор, конечно, гигант, но практическая ценность сомнительна.
m.s.moiseev; +1 Ответить
2. МимохожийОднако 121 29.03.17 08:41 Сейчас в теме
Хотелось бы примеры практического использования
3. so-quest 128 29.03.17 12:03 Сейчас в теме
Оригинально. Респект за такое.
chessman; +1 Ответить
4. chessman 178 29.03.17 12:58 Сейчас в теме
Как, в общем-то, правильно отметили, «труд», в большей степени, представляет теоретический интерес.
В качестве практического инструмента, на мой взгляд, можно было бы собирать статистику по выполняемым Запросам. Как вариант, записывать тексты запросов, время выполнения, размер выборки, дату, пользователя, для дальнейшего анализа.
То, что касается общего модуля DWX_Информатор, как я уже упоминал, его можно использовать самостоятельно. Например, для создания более универсальных алгоритмов. Допустим, к нам в процедуру приехал Объект. Нам нужно вызвать его экспортный метод «Метод1()», но мы не знаем, есть ли он у Объекта. Кроме как через Попытка…Исключение, сейчас понять это нельзя (поправьте, если ошибаюсь). Выглядит это несколько коряво. Используя DWX_Информатор, это можно сделать.
Думаю, что данный опус, также может быть полезен в качестве примера использования ассемблера. Возможно, это кого-то вдохновит на дальнейшие исследования.
5. sashocq 192 03.04.17 00:10 Сейчас в теме
Интересное исследование.

Есть база КА 2.2. Периодически в ЖР проскакивают записи с непонятным именем события. Можно ли как-то с помощью этого DynamicWrapperX перехватывать каждый вызов ЗаписьЖурналаРегистрации() и залоггировать его контекст выполнения и значения параметров?
6. chessman 178 03.04.17 23:16 Сейчас в теме
(5) Александр, у меня сейчас нет физической возможности проверить это на x64, но на x32 перехват работает.
Тут нужно понимать, что ловятся именно вызовы ЗаписьЖурналаРегистрации(), т.е. если в тексте модуля был вызван этот глобальный метод. Стандартная запись событий сюда не попадает.
То, что касается контекста выполнения, то пока помочь не смогу, хотя сам по себе вопрос довольно актуальный.

Чтобы заработал перехват в Вашем случае, нужно сделать отдельную сборку, поскольку речь идет о перехвате методов Глобальных контекстов. Сделать это не сложно, так что если интересно пишите.
7. sashocq 192 04.04.17 14:16 Сейчас в теме
(6) Владимир, именно это и нужно. Подозреваю, что где-то в коде неправильное имя события передаётся. Простой визуальный осмотр результатов поиска ЗаписьЖурналаРегистрации ничего не дал, т. к. ооочень много кода.

Спасибо, пока добавлю в закладки на будущее. Сейчас слишком большая загрузка по проектам, а вопрос этот не критичный.
Оставьте свое сообщение