Когда поступающий ORPC-запрос переадресуется потоку из кэша. поток выделяет из заголовка ORPC-вызова IPID (идентификатор указателя интерфейса) и находит соответствующий администратор заглушек и интерфейсную заглушку. Поток определяет тип того апартамента, в котором хранится объект, и если объект находится в апартаменте типа МТА или RTA, поток входит в апартамент объекта и вызывает метод IRpcStubBuffer::Invoke на интерфейсную заглушку. Если апартамент имеет тип RTA, то в течение вызова метода последующие потоки не будут допускаться к объекту. Если апартамент имеет тип МТА, то последующие потоки могут обращаться к объекту одновременно. В случае внутрипроцессных RTA/MTA-связей канал может сократить кэш потоков RPC и использовать поток клиента повторно, временно входя в апартамент объекта. Если бы МТА и RTA были единственными типами апартаментов, то этого было бы достаточно.
Диспетчеризация вызовов в STA более сложна, так как в существующий STA не могут войти никакие другие потоки. К сожалению, когда ORPC-запросы поступают от внехостовых клиентов, они координируются с использованием потоков из RPC кэша потоков, которые по определению не могут выполняться в STA объекта. Для того чтобы войти в STA и направить вызов потока STA, RPC-поток использует АРI-функцию PostMessage , которая ставит сообщение в специальную MSG -очередь сообщений STA-потоков, как показано на рис. 5.5. Эта очередь представляет собой ту же очередь FIFO (first-in, first-out), которую применяет оконная система. Это означает, что для завершения диспетчеризации вызова STA-поток должен обслуживать очередь с помощью одной из вариаций следующего кода:
MSG msg;
while (GetMessage(&msg, 0, О, 0))
DispatchMessage(&msg);
Этот код означает, что STA-поток имеет по меньшей мере одно окно, которое может принимать сообщения. Когда поток входит в новый STA посредством вызова CoInitializeEx , СОМ создает новое невидимое окно, вызывая CreateWindowEx. Это окно связано с зарегистрированным в СОМ оконным классом, элемент которого WndProc ищет определенные заранее оконные сообщения и обслуживает соответствующий ORPC-запрос посредством вызова метода IRpcStubBuffer::Invoke на интерфейсную заглушку. Отметим, что поскольку окна, подобно объектам на основе STA, обладают потоковой привязкой, WndProc будет выполняться в апартаменте объекта. Чтобы избежать чрезмерного количества переключения потоков, в версии СОМ для Windows 95 предусмотрен транспортный RPC-механизм, который обходит RPC-кэш потоков и вызывает PostMessage из потока вызывающего объекта. Этот перенос возможен только в том случае, если клиент находится на том же хосте, что и объект, поскольку API-функция PostMessage не работает в сети.
Для предотвращения взаимоблокировки все типы апартаментов СОМ поддерживают реентерабельность (повторную входимость)[1]. Когда поток в апартаменте делает запрос на объект вне апартамента вызывающего объекта посредством заместителя, то могут обслуживаться и другие поступающие запросы методов, пока поток вызывающего объекта находится в ожидании ORPC-ответа па первый запрос. Без этой поддержки было бы невозможно создавать системы, основанные на совместно работающих объектах. При написании следующего кода предполагалось, что CLSID_Callback является внутрипроцессным сервером, поддерживающим модель вызывающего потока, и что CLSID_Object является классом, сконфигурированным для активации на удаленной машине:
ICallback *pcb = 0;
HRESULT hr = CoCreateInstance(CLSID_Callback, 0, CLSCTX_ALL,
IID_ICallback, (void**)&pcb);
assert(SUCCEEDED(hr));
// callback object lives in this apt.
// объект обратного вызова живет в этом апартаменте
I0bject "po = 0;
hr = CoCreateInstance(CLSID_Object, 0, CLSCTX_REMOTE_SERVER,
IID_Iobject, (void **)&po);
assert(SUCCEEDED(hr));
// object lives in different apt.
// объект живет в другом апартаменте
// make a call to remote object, marshaling a reference to
// the callback object as an [in] parameter
// делаем вызов удаленного объекта, маршалируя ссылку
// на объект обратного вызова как на [in]-параметр
hr = po->UseCallback(pcb);
// clean up resources
// очищаем ресурсы
pcb->Release();
pco->Release();
На рис. 5.6 показано, что если апартамент вызывающего объекта не поддерживает реентерабельность, то следующая реализация метода UseCallback вызовет взаимоблокировку:
STDMETHODIMP Object::UseCallback(ICallback *pcb) {
HRESULT hr = pcb->GetBackToCallersApartment();
assert(SUCCEEDED(hr));
return S_OK;
Напомним, что когда [in]–параметр передается через метод заместителя UseCallback, то заместитель вызывает CoMarshalInterface для маршалинга интерфейсного указателя ICallback. Поскольку указатель ссылается на объект, находящийся в апартаменте вызывающего объекта, то этот апартамент становится экспортером объектов и поэтому любые межапартаментные вызовы объекта должны обслуживаться в апартаменте вызывающего объекта. Когда заглушка интерфейса IObject демаршалирует интерфейс ICallback, она создает заместитель для передачи его реализации метода UseCallback. Этот заместитель представляет объект при промежуточном соединении с объектом обратного вызова, которое продолжается на протяжении всего времени вызова. Время существования этого заместителя/соединения может превысить время вызова, если реализация метода просто вызовет AddRef на заместитель[2]: