45825 (Перехват методов COM интерфейсов)

2016-07-31СтудИзба

Описание файла

Документ из архива "Перехват методов COM интерфейсов", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.

Онлайн просмотр документа "45825"

Текст из документа "45825"

Перехват методов COM интерфейсов

Ivan Andreyev

Введение

В одной из статей RSDN Magazine описывался способ перехвата методов интерфейса IUnknown. Суть этого подхода заключалась в замене указателей на функции QueryInterace, AddRef, Release в VTBL интерфейса и выполнении дополнительной обработки внутри перехватчиков.

В этой статье мы продолжим обсуждение темы перехвата вызовов методов COM-интерфейсов и познакомимся с API-функциями CoGetInterceptor, CoGetInterceptorFromTypeInfo, позволяющими забыть обо всех технических трудностях и проблемах, связанных с передачей вызова от клиента перехватчику, и от перехватчика – исходному компоненту.

Технология “перехвата” вызовов API функций, обработчиков оконных сообщений, методов COM-компонентов имеет много общего с шаблоном проектирования Proxy (Заместитель). Суть этой технологии заключается в том, что вызов клиента перенаправляется (с помощью различных технических ухищрений – замена VTBL, Proxy-объект и т.п.) сначала коду заместителя, который выполняет пред- и постобработку, а затем уже – исходному объекту. Благодаря этому можно добавлять новую функциональность, никак не изменяя ни код клиента, ни код сервера.

Очень широкое распространение технология “перехвата” получила в COM – фундаментальные принципы прозрачности местонахождения компонента (location transparency) и прозрачности типа синхронизации (concurrency transparency) реализуются именно благодаря Proxy-компонентам из инфраструктуры COM, которые имитируют для клиента исходный компонент. С появлением COM+ набор сервисов, которые реализуют перехватчики, расширился еще больше – добавились поддержка транзакций, блокировок для синхронизации доступа к компонентам, поддержка just-in-time активации, ролевая безопасность. За счет того, что эти сервисы реализуются инфраструктурой COM+ прозрачно для клиента и серверных компонентов (хотя серверные COM+-компоненты могут взаимодействовать с инфраструктурой, например, чтобы отменить или подтвердить транзакцию), клиентский код ничего не знает о том, что случится с его вызовом на сервере – будет ли он обслуживаться COM+ или обычным COM-компонентом. Аналогично, один и тот же компонент может использоваться в составе COM+-приложения.

Помимо предоставления различных сервисов перехват вызовов методов COM-компонентов позволяет решить и другие задачи, например:

протоколирование вызовов COM-компонентов;

отладка – проверка значений аргументов, контроль подсчета ссылок;

специальный маршалинг;

использование альтернативных по отношению к RPC видов транспорта для передачи COM-вызовов (MSMQ, SOAP и т.п.);

асинхронные вызовы (заместитель сохраняет информацию о вызове и производит фактический вызов исходного компонента позднее).

Рисунок 1 иллюстрирует принцип перехвата вызовов COM-компонентов, Proxy и Stub – служебные компоненты, один из которых принимает вызовы от клиента, имитируя исходный компонент, а другой – передает эти вызовы компоненту, имитируя логику работы клиента. Именно по такой схеме работает маршалинг в COM, и по такой же схеме COM+ обеспечивает дополнительные сервисы (транзакции, блокировки и т.п.) для сконфигурированных компонентов.

Рисунок 1. Принцип перехвата COM-вызова.

Как это часто случается, несмотря на простое описание технологии перехвата, ее техническая реализация очень непростое дело, в особенности, когда речь идет об универсальном перехвате.

В первой части статьи мы познакомимся с различными техническими способами перехвата вызовов.

Техника перехвата вызовов

Один из самых простых и эффективных способов перехвата вызовов методов COM-компонента заключается в создании Proxy-компонента, реализующего нужный интерфейс и перенаправляющего вызовы исходному COM-компоненту.

ПРИМЕЧАНИЕ

Для COM-компонентов такой подход используется не только при перехвате вызовов, но еще и как средство повторного использования кода (code reuse), и носит название containment (включение).

В качестве примера рассмотрим стандартную реализацию IStream на основе памяти – CreateStreamOnHGlobal. Предположим, что нам необходимо ассоциировать имя с каждым потоком IStream, созданным с помощью CreateStreamOnHGlobal. Имя потока можно получить с помощью вызова IStream::Stat, но реализация IStream на основе памяти HGlobal всегда возвращает пустое имя. Мы можем поступить следующим образом:

создать компонент-обертку, поддерживающий IStream;

перенаправлять все вызовы IStream в стандартную реализацию CreateStreamOnHGlobal;

в методе IStream::Stat указывать имя потока.

class StreamOnMemory : public CComObjectRoot,

public IStream

{

public:

BEGIN_COM_MAP(StreamOnMemory)

COM_INTERFACE_ENTRY(IStream)

END_COM_MAP()

public:

// реализация IStream

STDMETHOD(Seek)(_LARGE_INTEGER dlibMove, ULONG dwOrigin,

_ULARGE_INTEGER * plibNewPosition)

{

return m_spStm->Seek(dlibMove, dwOrigin, plibNewPosition);

}

// остальные методы реализованы аналогично Seek

...

STDMETHOD(Stat)(tagSTATSTG * pstatstg, ULONG grfStatFlag)

{

HRESULT hr = m_spStm->Stat(pstatstg, grfStatFlag);

if( SUCCEEDED(hr) && (grfStatFlag & STATFLAG_NONAME) == 0)

{

pstatstg->pwcsName = AtlAllocTaskWideString(m_name);

}

return hr;

}

private:

friend HRESULT CreateStreamOnHGlobal2(HGLOBAL ,BOOL ,LPOLESTR, LPSTREAM*);

HRESULT init(HGLOBAL hGlobal,BOOL fDeleteOnRelease, LPOLESTR name)

{

m_spStm.Release();

HRESULT hr = CreateStreamOnHGlobal(hGlobal, fDeleteOnRelease, &m_spStm);

if(SUCCEEDED(hr))

{

m_name = name;

}

return hr;

}

private:

CComPtr m_spStm;

CComBSTR m_name;

};

HRESULT CreateStreamOnHGlobal2(HGLOBAL hGlobal,BOOL fDeleteOnRelease,

LPOLESTR name, LPSTREAM* ppstm)

{

CComObject* p = NULL;

HRESULT hr = CComObject::CreateInstance(&p);

if(SUCCEEDED(hr))

{

CComPtr spStm = p;

hr = p->init(hGlobal, fDeleteOnRelease, name);

if(SUCCEEDED(hr))

{

*ppstm = spStm.Detach();

}

}

return hr;

}

При таком подходе нет необходимости вносить какие-либо изменения в клиентский код, работающий с указателями на интерфейс IStream.

ПРИМЕЧАНИЕ

За исключением кода, создающего поток с помощью вызова CreateStreamOnHGlobal.

Такой “частный” подход неприменим, когда количество перехватываемых интерфейсов велико, или если информация об интерфейсах и сигнатурах их методов недоступна во время компиляции и станет известна только во время выполнения программы. Например, typelib-маршалинг в COM предоставляет клиенту Proxy-компонент, поддерживающий интерфейс серверного компонента, но обеспечить реализацию этого интерфейса инфраструктура COM может только во время выполнения – на этапе компиляции неизвестно, какие интерфейсы будут использоваться для typelib-маршалинга.

Разумеется, лучше было бы реализовать универсальный перехват вызовов COM-методов. Но при этом мы столкнемся с несколькими проблемами:

заранее неизвестно количество методов в произвольном интерфейсе, т.е. структура vtbl;

неизвестны сигнатуры индивидуальных методов, входящих в интерфейс, т.е. количество и типы параметров.

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

void f(int a, ...);

Но такие функции используют соглашение о вызове cdecl, а методы COM-интерфейсов – stdcall.

ПРИМЕЧАНИЕ

Эти соглашения о вызовах в первую очередь различаются тем, кто ответственен за удаление параметров из стека после вызова. stdcall-функции очищают стек сами, а для cdecl-функций стек очищает вызывающая функция.

Подход ATL

В библиотеке ATL перехват вызовов используется для отладки COM-серверов. Если до включения заголовочного файла объявить символ препроцессора _ATL_DEBUG_INTERFACES (или _ATL_DEBUG_REFCOUNT), то в окне “Output” отладчика VS во время выполнения приложения будут появляться сообщения, описывающие вызовы AddRef и Release для COM-объектов, созданных с помощью ATL, текущий счетчик ссылок или IID запрашиваемого интерфейса. Ниже приведен пример таких сообщений:

QIThunk-1 AddRef:Object=0x00da4c50 Refcount = 1 CComClassFactory - IUnknown

QIThunk-2 AddRef:Object=0x00da4c50 Refcount = 1 CComClassFactory - IClassFactory

QIThunk-3 AddRef:Object=0x00da4e20 Refcount = 1 CFoo - IFoo

QIThunk-3 AddRef:Object=0x00da4e20 Refcount = 2 CFoo - IFoo

QIThunk-3 Release:Object=0x00da4e20 Refcount = 1 CFoo - IFoo

QIThunk-2 Release:Object=0x00da4c50 Refcount = 0 CComClassFactory - IClassFactory

QIThunk-4 AddRef:Object=0x00da4e20 Refcount = 1 CFoo - IFoo

QIThunk-3 Release:Object=0x00da4e20 Refcount = 0 CFoo - IFoo

QIThunk-1 Release:Object=0x00da4c50 Refcount = 0 CComClassFactory - IUnknown

ATL: QIThunk-4 LEAK:Object = 0x00da4e20 Refcount = 1 MaxRefCount = 1 CFoo - IFoo

Во время выгрузки ATL COM-сервера в окне “Output” появятся сведения об указателях на интерфейс, для которых счетчик ссылок не достиг значения 0, т.е. об утечках COM объектов.

“Магия” ATL работает благодаря перехвату вызовов методов COM-интерфейсов, в частности, AddRef, Release и QueryInterface.

Когда клиент запрашивает интерфейс у объекта с помощью QueryInterface, класс CComObject делегирует вызов базовому классу CComObjectRootBase::InternalQueryInterface, который при определенном макросе _ATL_DEBUG_INTERFACES обращается к экземпляру класса CAtlDebugInterfacesModule и вызывает у него метод AddThunk.

HRESULT AddThunk(IUnknown** pp, LPCTSTR lpsz, REFIID iid) throw()

Результатом вызова CComObjectRootBase::InternalQueryInterface становится специальный объект-посредник QIThunk, который перехватывает AddRef, Release и QueryInterface, а все остальные вызовы делегирует исходному компоненту.

Класс CAtlDebugInterfacesModule хранит список всех активных объектов-заместителей QIThunk и в своем деструкторе выполняет отладочную печать всех объектов, чей счетчик ссылок не достиг нулевого значения.

Когда клиент отпускает последнюю ссылку на компонент, QIThunk удаляет себя из списка активных посредников в CAtlDebugInterfacesModule.

Таким образом, клиенты имеют дело не с прямым указателем на интерфейс COM-объекта, а с указателем на QIThunk, который и печатает отладочные сообщения о текущем значении счетчика ссылок и IID запрашиваемого интерфейса.

Указатель на QIThunk ведет себя в точности так же, как и указатель на обычный интерфейс. Это достигается за счет того, что vtbl класса QIThunk содержит адреса методов-перехватчиков, вызывающих исходные методы. Поскольку все интерфейсы унаследованы от IUnknown, первые три адреса vtbl содержат QueryInterface, AddRef и Release. Их реализация в QIThunk тривиальна – сигнатура методов в точности известна на этапе компиляции.

Но как быть с остальными методами интерфейса, количество и сигнатуры которых неизвестны? Для решения этой проблемы QIThunk использует универсальную функцию-перехватчик, адресом которой заполняется vtbl. Виртуальные методы объявляются в QIThunk так:

STDMETHOD(f3)();

STDMETHOD(f4)();

...

STDMETHOD(f1023)();

Vtbl QIThunk содержит 1024 адреса. Интерфейсы, объявляющие большее количество методов, встречаются нечасто.

Реализация этих методов задается с помощью макроса:

ATL_IMPL_THUNK(3)

ATL_IMPL_THUNK(4)

...

ATL_IMPL_THUNK(1023)

Метод-перехватчик будет вызываться клиентом с заранее неизвестным количеством параметров, поэтому написать такую функцию на языке высокого уровня невозможно – не подходят ни стандартные пролог/эпилог, генерируемые компилятором C++, ни “нормальное” завершение функции вызовом инструкции ret, так как stdcall-функции должны очищать стек сами, передавая размер стека параметров в ret.

Рисунок 2. Вызов COM метода.

Свежие статьи
Популярно сейчас
Как Вы думаете, сколько людей до Вас делали точно такое же задание? 99% студентов выполняют точно такие же задания, как и их предшественники год назад. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Нашёл ошибку?
Или хочешь предложить что-то улучшить на этой странице? Напиши об этом и получи бонус!
Бонус рассчитывается индивидуально в каждом случае и может быть в виде баллов или бесплатной услуги от студизбы.
Предложить исправление
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5137
Авторов
на СтудИзбе
441
Средний доход
с одного платного файла
Обучение Подробнее