45825 (Перехват методов COM интерфейсов), страница 3
Описание файла
Документ из архива "Перехват методов COM интерфейсов", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.
Онлайн просмотр документа "45825"
Текст 3 страницы из документа "45825"
Рисунок 3. Стек вызова
__declspec(naked) void ItfThunk::thunk() { __asm { push [esp] // кладем в стек n (параметр метода preprocess) push [esp+0Ch] // кладем в стек this для вызова preprocess call preprocess // вызываем ItfThunk::preprocess(n) call store // вызываем ItfThunk::store mov eax, [esp+8] // заменяем this в стеке на исходный mov eax, [eax+4] // из переменной ItfThunk::m_p mov [esp+8], eax lea eax, post_thunk // заменяем адрес возврата на post_thunk mov [esp+4], eax mov eax,[esp+8] // получаем vptr из исходного указателя mov eax, [eax] pop ecx // убираем из стека лишний параметр n mov eax, [eax+4*ecx] // полчаем адрес метода из vtbl jmp eax // переходим в исходный метод post_thunk: sub esp, 10h // выделяем в стеке место для CallInfo push esp push eax // результат вызова исходного метода в eax call restore // восстанавливаем инфрмацию из TLS add esp,8 call postprocess // постобработка ret } } |
Использовать перехватчик очень просто – клиент передает указатель на настоящий интерфейс конструктору ItfThunk и затем использует ItfThunk в качестве указателя:
CComPtr spFoo; HRESULT hr = spFoo.CoCreateInstance(__uuidof(Foo)); thunks::ItfThunk t(spFoo.p); spFoo.p = reinterpret_cast(&t); spFoo->F(); |
Теперь мы можем выполнять постобработку вызова, но есть еще одна задача, которую этот перехватчик не решает – предположим, что в некоторых случаях в результате предобработки мы принимаем решение, что вызов исходного метода должен быть заблокирован. Типичный пример – ролевая безопасность. Вызов метода не проходит проверку ролевой безопасности и должен быть отклонен. Но мы не можем сделать этого, так как точное количество параметров метода неизвестно, и наш перехватчик делегирует очистку стека после вызова самому методу.
В общем случае для COM-интерфейсов мы не можем узнать сигнатуру их методов, но для интерфейсов, использующих typelib-маршалинг или итерфейсов, proxy/stub которых сгенерирован с ключом MIDL /oicf, эта информация доступна.
ПРИМЕЧАНИЕ Ключ /oicf компилятора midl позволяет генерировать интерпретируемый код для proxy/stub и, как результат, информация о сигнатурах метода доступна программно. Подробнее об этом можно прочитать в статье “Секреты маршалинга”. |
Получив информацию о количестве параметров метода, мы смогли решить несколько задач:
Заблокировать вызов метода.
Выполнять отложенный/асинхронный вызов.
И все это благодаря тому, что перехватчик сможет очищать стек самостоятельно, не делегируя эту работу исходному методу.
Необходимости самостоятельно разрабатывать перехватчик, опирающийся на информацию из библиотеки типов, нет – начиная с W2K документирован API, позволяющий использовать стандартные перехватчики из инфраструктуры COM/COM+ в своих целях.
CoGetInterceptor, CoGetInterceptorFromTypeInfo
В предыдущем разделе статьи мы рассмотрели несколько технологий перехвата вызовов методов интерфейсов (и могли почувствовать сложность создания универсального перехватчика). Но ни одна из этих технологий не позволила решить задачу перехвата полностью. В частности, не решена задача асинхронных/отложенных вызовов.
К нашей радости, теперь документированы API-функции, позволяющие использовать в приложениях перехватчики из инфраструктуры COM/COM+.
ПРИМЕЧАНИЕ Это те самые перехватчики, с помощью которых COM+ обеспечивает свои сервисы прозрачно для компонента и клиента – ролевую безопасность, синхронизацию и т.д. |
Получить перехватчик для произвольного интерфейса можно с помощью функции CoGetInterceptor:
HRESULT CoGetInterceptor( REFIID iidIntercepted, // IID перехватываемого интерфейса IUnknown * punkOuter, // IUnknown для агрегации REFIID iid, // IID интерфейса, запрашиваемого у перехватчика void ** ppv // указатель на интерфейс перехватчика ); |
Перехватчики COM+ используют информацию из библиотеки типов, чтобы определить сигнатуру метода и количество/типы параметров, а также выполнить маршалинг. Поэтому, если быть более точным, в качестве первого параметра (iidInterceptor) годятся не произвольные интерфейсы, а только те из них, которые совместимы с oleautomation и описаны в библиотеке типов.
Основной интерфейс перехватчика – ICallInterceptor, его мы и будем запрашивать в вызове CoGetInterceptor:
#include CComModule _Module; int _tmain(int argc, _TCHAR* argv[]) { CoInitialize( 0); _Module.Init(0, 0 ); { CComPtr spFoo; HRESULT hr = spFoo.CoCreateInstance(__uuidof(Foo)); CComPtr spInt; hr = CoGetInterceptor(__uuidof(IFoo), 0, __uuidof(ICallInterceptor), reinterpret_cast(&spInt)); } _Module.Term(); CoUninitialize(); return 0; } |
Результатом выполнения приведенного выше приложения будет … Access Violation в недрах ntdll.dll. Этот неприятный сюрприз вызван тем, что перехватчики используют распределитель памяти RPC, который по умолчанию не проинициализирован. Исправить эту проблему можно либо с помощью вызова CoInitializeSecurity, либо вызовом любых функций маршалинга, которые проинициализируют RPC heap (есть еще вариант с прямым вызовом функции инициализации из rpcrt4.dll, но она не документирована).
ПРИМЕЧАНИЕ Проблема с инициализацией RPC-кучи была исправлена в Windows 2003 Server. |
Исправленный код клиента:
HRESULT hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); CComPtr spFoo; hr = spFoo.CoCreateInstance(__uuidof(Foo)); CComPtr spInt; hr = CoGetInterceptor(__uuidof(IFoo), 0, __uuidof(ICallInterceptor), reinterpret_cast(&spInt)); |
С помощью указателя на интерфейс ICallInterceptor мы можем зарегистрировать свои собственные обработчики вызовов:
Методы ICallInterceptor | Описание |
HRESULT RegisterSink(ICallFrameEvents * psink); | Зарегистрировать обработчик |
HRESULT GetRegisteredSink(ICallFrameEvents ** ppsink); | Получить зарегистрированный обработчик |
ПРИМЕЧАНИЕ Другие методы ICallInterceptor описаны в MSDN |
Обработчик должен реализовать интерфейс ICallFrameEvents.
Методы ICallFraneEvent | Описание |
HRESULT OnCall(ICallFrame * pFrame); | Вызов метода перехватываемого интерфейса |
После регистрации обработчика мы будем получать событие OnCall каждый раз, когда клиент будет осуществлять вызов через перехватываемый интерфейс.
Дополним код клиента (см. выше) – теперь мы будем регистрировать свой обработчик вызовов:
class CallHandler : public CComObjectRoot, public ICallFrameEvents { public: BEGIN_COM_MAP(CallHandler) COM_INTERFACE_ENTRY(ICallFrameEvents) END_COM_MAP() STDMETHOD(OnCall)(ICallFrame* pFrame) { return S_OK; } }; ... CComPtr spInt; hr = CoGetInterceptor(__uuidof(IFoo), 0, __uuidof(ICallInterceptor), reinterpret_cast(&spInt)); CComObject* pHandler = 0; CComObject::CreateInstance(&pHandler); hr = spInt->RegisterSink(pHandler); CComPtr spFooInt; hr = spInt.QueryInterface(&spFooInt); hr = spFooInt->F(); |
ПРИМЕЧАНИЕ Если обработчик вернет HRESULT с ошибкой, ошибку получит и клиент, но ее код, к сожалению, не передается пользователю. Если клиент не зарегистрирует ни одного обработчика, то вызов метода также завершится с ошибкой. |
Мы запрашиваем указатель на перехватываемый интерфейс у перехватчика, а затем выполняем вызов метода IFoo::F, в результате мы попадем в код обработчика ICallFrameEvent::OnCall.
Задача обработчика – решить, что делать дальше с вызовом:
Отклонить его, вернув ошибку.
Сохранить стек параметров вызова, чтобы выполнить его асинхронно.
Выполнить вызов немедленно.
Прямые/синхронные вызовы
Информацию о вызове обработчик получает с помощью указателя на интерфейс ICallFrame, передаваемый ему в качестве параметра pFrame.
Интерфейс ICallFrame позволяет получить информацию о сигнатуре метода, размере стека параметров, значения отдельных параметров и результат вызова метода. Кроме того, с помощью ICallFrame можно изменить значения отдельных (или всех) параметров и дополнить стек параметров в случае, если клиент передал не все необходимые параметры (например, клиент сделал вызов не через указатель на перехватываемый интерфейс, а с помощью ICallInterceptor::CallIndirect, передавая частичный стек параметров).
ПРИМЕЧАНИЕ Подробнее описание методов интерфейса ICallFrame см. в MSDN |
Расширим код нашего обработчика CallHandler так, чтобы он выдавал отладочные сообщения о вызове и его результатах и выполнял немедленный вызов с помощью ICallFrame::Invoke:
template class CallHandler : public CComObjectRoot, public ICallFrameEvents { public: BEGIN_COM_MAP(CallHandler) COM_INTERFACE_ENTRY(ICallFrameEvents) END_COM_MAP() void init(CComPtr spItf) { m_spItf = spItf; } STDMETHOD(OnCall)(ICallFrame* pFrame) { LPWSTR itf, method; HRESULT hr = pFrame->GetNames(&itf, &method); hr = pFrame->Invoke(m_spItf.p); ATLTRACE("call %s::%s %8x\n", itf, method, hr); CoTaskMemFree(itf); CoTaskMemFree(method); return hr; } private: CComPtr m_spItf; }; |
Вызывая ICallFrame::Invoke, мы не передаем никаких параметров – значения для параметров перехватываемого метода были переданы клиентом, когда он выполнял вызов через перехватчик.
ПРИМЕЧАНИЕ Метод ICallFrame::Invoke имеет переменное количество параметров (что редко встречается у COM-интерфейсов). Если стек параметров вызова заполнен только частично, в Invoke могут передаваться дополнительные параметры вызова (которые будут добавлены в стек перед вызовом). |
Косвенные и асинхронные/отложенные вызовы
Мы научились выполнять прямые вызовы через указатель на перехватываемый интерфейс. Такой перехватчик может выполнять трассировку вызовов и их результатов, облегчать процесс отладки сложных компонентов, отслеживать значения отдельных параметров (и заменять их в целях отладки).
С помощью перехватчиков COM+ можно выполнять косвенные и асинхронные вызовы. Вместо прямого вызова ICallFrame::Invoke мы можем:
сохранить содержимое параметров, находящихся в стеке, в специальный буфер (фактически выполнить маршалинг параметров);
передать их с помощью любого доступного транспорта (RPC, MSMQ, SOAP, файлы и т.п.) компоненту;
выполнить вызов;