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

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

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

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

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

Текст 4 страницы из документа "45825"

получить значения [out] параметров, выполнить обратный маршалинг;

передать значения параметров клиенту с помощью любого доступного транспорта.

Для упаковки стека вызова, т.е. маршалинга предназначен метод ICallFrame::Marshal:

HRESULT Marshal(

CALLFRAME_MARSHALCONTEXT * pmshlContext, // контекст (т.e. inproc и т.п.)

MSHLFLAGS * mshlflags, // обычный или табличный маршалинг

PVOID pBuffer, // буфер

ULONG cbBuffer, // размер буфера

ULONG * pcBufferUsed, // использованный размер буфера

RPCOLEDATAREP * pdataRep, // формат представления данных

ULONG * prpcFlags // RPC-флаги

);

Размер буфера, необходимого для маршалинга, можно определить с помощью ICallFrame::GetMarshalSizeMax:

HRESULT GetMarshalSizeMax(

CALLFRAME_MARSHALCONTEXT * pmshlContext, // контекст (т.e. inproc и т.п.)

MSHLFLAGS mshlflags, // обычный или табличный маршалинг

ULONG * pcbBufferNeeded // необходимый размер буфера

);

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

HRESULT Unmarshal(

ULONG iMethod, // номер метода

PVOID pBuffer, // буфер

ULONG cbBuffer, // размер буфера

BOOL fForceBufferCopy, // сохранить копию буфера

RPCOLEDATAREP dataRep, // формат представления данных

CALLFRAME_MARSHALCONTEXT * pcontext, // контекст (т.e. inproc и т.п.)

ULONG * pcbUnmarshalled, // размер использованной части буфера

ICallFrame ** ppFrame // ICallFrame со стеком вызова

);

Интерфейс ICallUnmarshal поддерживается перехватчиком, который мы получаем вызовом CoGetInterceptor. Таким образом, чтобы преобразовать буфер в стек вызова, нам необходимо:

создать перехватчик в адресном пространстве сервера (т.е. вызываемого компонента);

запросить у него (через QI) указатель на интерфейс ICallUnmarshal;

вызывать ICallUnmarshal::Unmarshal – мы получим указатель на интерфейс ICallFrame.

После вызова компонента обычно нужно передать выходные (out) параметры обратно клиенту. Сделать это можно парой вызовов:

ICallFrame::Marshal на серверной стороне;

ICallFrame::Unmarshal на стороне клиента.

HRESULT UnMarshal(

PVOID pBuffer, // буфер с out-параметрами

ULONG cbBuffer, // размер буфера

RPCOLEDATAREP pdataRep, // формат представления данных

CALLFRAME_MARSHALCONTEXT * pcontext, // контекст (т.e. inproc и т.п.)

ULONG * pcbUnmarshaled // размер использованной части буфера

);

Тип маршалинга параметров – in или out – задается флагом структуры CALLFRAME_MARSHALCONTEXT.

Последовательность вызовов при маршалинге in- и out-параметров проиллюстрирована на рисунке 4.

Рисунок 4. Маршалинг параметров.

В качестве примера, использующего возможности маршалинга параметров, разработаем перехватчик, передающий вызовы серверному компоненту не с помощью традиционного в таких случаях RPC, а через очереди MSMQ (Microsoft Message Queueing).

ПРИМЕЧАНИЕ

В COM+ имеется поддержка MSMQ в качестве транспорта. Для COM+-компонентов (такие компоненты называются “queued components”) с помощью MSMQ выполняются асинхронные вызовы, т.е. клиент не ждет завершения вызова и, следовательно, значения out-параметров клиенту не передаются. В нашем примере мы будем выполнять синхронные вызовы с передачей out-параметров клиенту.

Для общения с сервером нам потребуются 2 очереди MSMQ: для сообщений с in-параметрами и с out-параметрами. Мы будем использовать private-очереди, т.е. очереди, доступ к которым возможен только по полному пути с указанием имени компьютера.

ПРИМЕЧАНИЕ

Альтернативный тип очередей MSMQ: public-очереди. Информация о них хранится в Active Directory и доступ к ним возможен по имени (без указания полного пути).

Для работы с очередями нам понадобится функция CreateQueue, создающая private-очередь (в качестве имени подойдет GUID, сгенерированный функцией CoCreateGuid):

HRESULT CreateQueue(CComBSTR& queue)

{

CLSID guid = CLSID_NULL;

::CoCreateGuid(&guid);

CComBSTR path = L".\\Private$\\";

path.Append(guid);

const int NumberOfProps = 1;

MQPROPVARIANT aQueuePropVar[NumberOfProps];

aQueuePropVar[0].vt = VT_LPWSTR;

aQueuePropVar[0].pwszVal = path;

QUEUEPROPID aQueuePropId[NumberOfProps] = { PROPID_Q_PATHNAME };

HRESULT aQueueStatus[NumberOfProps] = { S_OK };

MQQUEUEPROPS props;

props.cProp = NumberOfProps;

props.aPropID = aQueuePropId;

props.aPropVar = aQueuePropVar;

props.aStatus = aQueueStatus;

WCHAR buffer[256];

DWORD dwLen = sizeof(buffer)/sizeof(buffer[0]);

HRESULT hr = ::MQCreateQueue(0, &props, buffer, &dwLen);

if(SUCCEEDED(hr))

{

queue = buffer;

}

return hr;

}

Еще нам потребуется класс Queue, позволяющий отправлять и получать сообщения методами Send и Receive (в синхронном режиме с ожиданием появления сообщения):

class Queue

{

public:

Queue() : m_hQueue(0) {}

HRESULT Init(LPWSTR name, DWORD dwAccess)

{

Close();

return MQOpenQueue(name, dwAccess, MQ_DENY_NONE, &m_hQueue);

}

HRESULT Send(BYTE* buffer, DWORD cbSize)

{

const int NumberOfProps = 2;

PROPVARIANT aMsgPropVar[NumberOfProps];

aMsgPropVar[0].vt = VT_VECTOR | VT_UI1;

aMsgPropVar[0].caub.pElems = buffer;

aMsgPropVar[0].caub.cElems = cbSize;

aMsgPropVar[1].vt = VT_UI4;

aMsgPropVar[1].lVal = VT_ARRAY | VT_UI1;

MSGPROPID aMsgPropId[NumberOfProps]={PROPID_M_BODY, PROPID_M_BODY_TYPE};

HRESULT aMsgStatus[NumberOfProps] = {S_OK, S_OK};

MQMSGPROPS msgprops;

msgprops.cProp = NumberOfProps;

msgprops.aPropID = aMsgPropId;

msgprops.aPropVar = aMsgPropVar;

msgprops.aStatus = aMsgStatus;

return MQSendMessage(m_hQueue, &msgprops, MQ_NO_TRANSACTION);

}

HRESULT Receive(BYTE** pBuffer, DWORD* pcbSize, DWORD timeout = INFINITE)

{

const int NumberOfProps = 2;

PROPVARIANT aMsgPropVar[NumberOfProps];

aMsgPropVar[0].vt = VT_VECTOR | VT_UI1;

aMsgPropVar[0].caub.pElems = 0;

aMsgPropVar[0].caub.cElems = 0;

aMsgPropVar[1].vt = VT_NULL;

MSGPROPID aMsgPropId[NumberOfProps]={PROPID_M_BODY, PROPID_M_BODY_SIZE};

HRESULT aMsgStatus[NumberOfProps] = {S_OK, S_OK};

MQMSGPROPS msgprops;

msgprops.cProp = NumberOfProps;

msgprops.aPropID = aMsgPropId;

msgprops.aPropVar = aMsgPropVar;

msgprops.aStatus = aMsgStatus;

HRESULT hr = MQReceiveMessage(m_hQueue, timeout, MQ_ACTION_RECEIVE,

&msgprops, 0, 0, 0, MQ_SINGLE_MESSAGE);

if(hr == MQ_ERROR_BUFFER_OVERFLOW)

{

aMsgPropVar[0].caub.pElems =

reinterpret_cast(malloc(aMsgPropVar[1].lVal));

aMsgPropVar[0].caub.cElems = aMsgPropVar[1].lVal;

hr = MQReceiveMessage(m_hQueue, timeout, MQ_ACTION_RECEIVE,

&msgprops, 0, 0, 0, MQ_SINGLE_MESSAGE);

if(SUCCEEDED(hr))

{

*pBuffer = aMsgPropVar[0].caub.pElems;

*pcbSize = aMsgPropVar[0].caub.cElems;

}

else

{

free(aMsgPropVar[0].caub.pElems);

}

}

return hr;

}

HRESULT Close()

{

HRESULT hr = S_OK;

if(m_hQueue)

{

hr = ::MQCloseQueue(m_hQueue);

m_hQueue = 0;

}

return hr;

}

~Queue()

{

Close();

}

private:

QUEUEHANDLE m_hQueue;

};

В методе обработчика вызова ICallFrameEvents::OnCall (см. пример выше) вместо прямого вызова исходного компонента с помощью ICallFrame::Invoke мы будем выполнять маршалинг in-параметров, передачу их через очереди MSMQ и обратное преобразование для out-параметров из буфера маршалинга в стек вызова. Помимо преобразованных в буфер маршалинга in-параметров на приемной стороне нам потребуется информация о IID перехватываемого интерфейса и номере вызываемого метода. Эти данные мы будем передавать в заголовке запроса:

struct CallHdr // заголовок запроса

{

CLSID coclass; // CLSID исходного компонента

IID itf; // IID перехватываемого интерфейса

ULONG method; // номер перехватываемого метода

ULONG rep; // формат представления данных RPC

};

...

// устанавливаем контекст маршалинга – in-параметры, MSHCTX_INPROC

CALLFRAME_MARSHALCONTEXT ctx = {TRUE, MSHCTX_INPROC};

DWORD cbSize = 0;

// определяем размер, необходимый для буфера

HRESULT hr = pFrame->GetMarshalSizeMax(&ctx, MSHLFLAGS_NORMAL, &cbSize);

if(SUCCEEDED(hr))

{

cbSize += sizeof(CallHdr);

BYTE* pBuffer = reinterpret_cast(malloc(cbSize));

CallHdr* pHdr =

reinterpret_cast(pBuffer);

ULONG rep = 0;

// маршалинг in-параметров в буфер

hr = pFrame->Marshal(&ctx, MSHLFLAGS_NORMAL, pBuffer +

sizeof(CallHdr), cbSize, &cbSize, &rep, 0);

if(SUCCEEDED(hr))

{

pHdr->rep = rep;

// получаем у перехватчика номер метода и IID интерфейса для заголовка

hr = pFrame->GetIIDAndMethod(&pHdr->itf, &pHdr->method);

if(SUCCEEDED(hr))

{

pHdr->coclass = m_coclass;

// отправляем запрос серверу

hr = m_qin.Send(pBuffer, cbSize + sizeof(CallHdr));

if(SUCCEEDED(hr))

{

free(pBuffer);

pBuffer = 0;

cbSize = 0;

// получаем отклик сервера с out-параметрами

hr = m_qout.Receive(&pBuffer, &cbSize);

if(SUCCEEDED(hr))

{

// восстанавливаем значения out-параметров и HRESULT

// вызова метода на сервере

hr = pFrame->Unmarshal(pBuffer, cbSize, rep, &ctx, &cbSize);

}

}

}

}

if(pBuffer)

{

free(pBuffer);

}

}

return hr;

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

mq::Queue qin, qout;

qin.Init(pInfo->queue_in, MQ_RECEIVE_ACCESS);

qout.Init(pInfo->queue_out, MQ_SEND_ACCESS);

while(true)

{

const DWORD timeout = 500;

BYTE* pBuffer = NULL;

DWORD cbSize = 0;

// получаем запрос от клиента

HRESULT hr = qin.Receive(&pBuffer, &cbSize, timeout);

if(SUCCEEDED(hr))

{

CallHdr* pHdr = reinterpret_cast(pBuffer);

cbSize -= sizeof(CallHdr);

CComPtr spUnmarshal;

// создаем перехватчик на серверной стороне

hr = CoGetInterceptor(pHdr->itf, 0, IID_ICallUnmarshal,

(void**)&spUnmarshal);

if(SUCCEEDED(hr))

{

CComPtr spFrame;

CALLFRAME_MARSHALCONTEXT ctx = { TRUE, MSHCTX_INPROC };

// выполняем преобразование буфера маршалинга в стек вызова

hr = spUnmarshal->Unmarshal(pHdr->method, pBuffer + sizeof(CallHdr),

cbSize, FALSE, pHdr->rep, &ctx, &cbSize, &spFrame);

if(SUCCEEDED(hr))

{

CComPtr spUnk;

// создаем экземпляр компонента

hr = CoCreateInstance(pHdr->coclass, 0, CLSCTX_ALL,

pHdr->itf, (void**)&spUnk);

if(SUCCEEDED(hr))

{

// вызываем исходный метод

hr = spFrame->Invoke(spUnk.p);

if(SUCCEEDED(hr))

{

ctx.fIn = FALSE;

cbSize = 0;

free(pBuffer);

pBuffer = NULL;

// маршалинг out-параметров и результата HRESULT вызова

hr = spFrame->GetMarshalSizeMax(&ctx,

MSHLFLAGS_NORMAL, &cbSize);

if(SUCCEEDED(hr))

{

pBuffer = reinterpret_cast(malloc(cbSize));

hr = spFrame->Marshal(&ctx, MSHLFLAGS_NORMAL,

pBuffer, cbSize, &cbSize, 0, 0);

}

}

}

}

}

if(FAILED(hr))

{

cbSize = sizeof(HRESULT);

*reinterpret_cast(pBuffer) = htonl(hr);

}

// отправляем ответ клиенту

hr = qout.Send(pBuffer, cbSize);

if(pBuffer)

{

free(pBuffer);

}

}

if(WaitForSingleObject(pInfo->hShutdown, timeout) == WAIT_OBJECT_0)

break;

}

ПРЕДУПРЕЖДЕНИЕ

В методе ICallUnmarshal::Unmarshal явно указывается номер метода (первый параметр). Хотя в документации сказано, что возможное значение этого параметра -1 (в этом случае перехватчик сам определит номер метода, прочитав эту информацию из буфера с данными маршалинга), на практике такое значение использовать не удалось – при использовании -1 вызов ICallUnmarshal::Unmarshal завершается ошибкой доступа к памяти Access Violation в модуле ole32.dll

В рассмотренном выше примере нам не пришлось внести ни одного изменения ни в код клиента, ни в код компонента – благодаря технологии перехвата вызовов вся работа по организации взаимодействия клиента с компонентом происходит для них абсолютно прозрачно.

Использование такого “ручного” маршалинга параметров позволяет нам увидеть, какой информацией обмениваются proxy/stub в стандартной инфраструктуре COM. На иллюстрации приведен пример буфера, содержащего строчку BSTR, массив SAFEARRAY и объектную ссылку (указатель на интерфейс):

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