45825 (Перехват методов COM интерфейсов), страница 4
Описание файла
Документ из архива "Перехват методов 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 и объектную ссылку (указатель на интерфейс):