Выбрать главу

Чтобы распознавать распределенные OXID, служба OR на каждой хост-машине ждет удаленные запросы на распознавание OXID на известном адресе (порт 135 для TCP и UDP) для каждого поддерживаемого сетевого протокола. Когда запрос на распознавание OXID получен из сети, опрашивается локальная таблица OXID. Запрос на распознавание покажет, какие сетевые протоколы поддерживает машина клиента. Если запрошенный процесс из апартамента еще не начал использовать один из запрошенных протоколов, то OR свяжется с СОМ-библиотекой в апартаменте объекта, используя локальный IРС, и инициирует использование запрошенного протокола в процессе объекта. Как только это произойдет, OR занесет новый сетевой адрес апартамента в локальную таблицу OXID. После записи новый сетевой адрес возвращается к OR демаршалирующей стороны, где он кэшируется, чтобы предотвратить дополнительные сетевые запросы на часто используемые апартаменты. Может показаться странным, что контрольное считывание полностью квалифицированных сетевых адресов в ссылке маршалировапного объекта не выполняется сразу. Но уровень косвенности, который допускают не зависящие от протокола идентификаторы апартаментов (OXID), разрешает процессу на основе СОМ откладывать использование сетевых протоколов до тех пор, пока они не потребуются. Это особенно важно, поскольку СОМ может иметь дело с множеством различных протоколов (не только TCP), и требовать от каждого процесса слежения за запросами с использованием всех поддерживаемых протоколов было бы чрезвычайно неэффективно. Фактически, если СОМ-процесс никогда не экспортирует указатели внехостовым клиентам, он никогда не потратит вообще никаких сетевых ресурсов.

Вспомогательные средства для внутрипроцессного маршалинга

Хотя фрагменты кода для WritePtr и ReadPtr из предыдущего раздела достаточно просто реализовать, большинство явных вызовов CoMarshalInterface будут использоваться для передачи интерфейсного указателя от одного потока к другому в том же самом процессе. Для упрощения этой задачи в СОМ предусмотрены две оберточные функции (wrapper functions), которые реализуют нужный стандартный код вокруг CoMarshalInterface и CoUnmarshalInterface. API-функция СОМ CoMarshalInterThreadInterfaceInStream

HRESULT CoMarshalInterThreadInterfaceInStream( [in] REFIID riid, [in, iid_is(riid)] IUnknown *pItf, [out] IStream **ppStm );

обеспечивает простую обертку вокруг CreateStreamOnHGlobal и CoMarshalInterface, как показано ниже:

// from OLE32.DLL (approx.)

// из OLE32.DLL (приблизительно)

HRESULT CoMarshalInterThreadInterfaceInStream( REFIID riid, IUnknown *pItf, IStream **ppStm) {

HRESULT hr = CreateStreamOnHGlobal(0, TRUE, ppStm);

if (SUCCEEDED(hr))

hr = CoMarshalInterface(*ppStm, riid, pItf, MSHCTX_INPROC, 0, MSHLFLAGS_NORMAL);

return hr;

}

В СОМ предусмотрена также обертка вокруг CoUnmarshalInterface:

HRESULT CoGetInterfaceAndReleaseStream( [in] IStream *pStm, [in] REFIID riid, [out, iid_is(riid)] void **ppv );

которая является очень тонкой оберткой вокруг CoUnmarshalInterface:

// from OLE32.DLL (approx.)

// из OLE32.DLL (приблизительно)

HRESULT CoGetInterfaceAndReleaseStream( IStream *pStm, REFIID riid, void **ppv)

{

HRESULT hr = CoUnmarshalInterface(pStm, riid, ppv);

pStm->Release();

return hr;

}

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

Следующий фрагмент кода мог бы быть применен для передачи интерфейсного указателя в другой апартамент того же процесса с использованием глобальной переменной для хранения промежуточной маршалированной объектной ссылки:

HRESULT WritePtrToGlobalVarable(IRacer *pRacer)

{

// where to write the marshaled ptr

// куда записывать маршалированный указатель

extern IStream *g_pStmPtr;

// thread synchronization for read/write

// синхронизация потока для чтения/записи

extern HANDLE g_heventWritten;

// write marshaled object reference to global variable

// записываем маршалированную объектную ссыпку

// в глобальную переменную

HRESULT hr = CoMarshalInterThreadInterfaceInStream( IID_IRacer, pRacer, &g_pStmPtr);

// signal other thread that ptr is now available

// подаем сигнал другому процессу о том, что указатель

// теперь доступен

SetEvent (g_heventWritten); return hr; }

Соответствующий код будет корректно демаршалировать объектную ссылку в апартамент вызывающего объекта при условии, что он находится и том же самом процессе:

HRESULT ReadPtrFromGlobalVariable(IRacer * &rpRacer) {

// where to write the marshaled ptr

// куда записывать маршалированный указатель

extern IStream *g_pStmPtr;

// thread synchronization for read/write

// синхронизация потока для чтения/записи extern

HANDLE g_heventWritten;

// wait for other thread to signal that ptr is available

// ожидаем другой поток, чтобы дать сигнал о том. что

// указатель доступен

WaitForSingleObject(g_heventWritten, INFINITE);

// read marshaled object reference from global variable

// читаем маршалированную объектную ссылку из глобальной переменной

HRESULT hr = CoGetInterfaceAndReleaseStream( g_pStmPtr, IID_IRacer. (void**) &rpRacer);

// MSHLFLAGS_NORMAL means no more unmarshals are legal

// MSHLFLAGS_NORMAL означает, что больше не может быть

// извлечено никаких демаршалированных указателей

g_pStmPtr = 0;

return hr;

}

Данный код требуется при передаче указателя от одного апартамента к другому[1]. Отметим, что при передаче указателя от потока, выполняющегося в МТА или RTA, к другому потоку, выполняющемуся в том же апартаменте, не требуется никаких вызовов маршалинга. Тем не менее, обычной практикой для программы записи (writer) интерфейсного указателя является вызов AddRef до передачи копии в поток программы считывания (reader). Когда поток читающей программы выполняется с использованием указателя, ему, конечно, необходимо будет вызвать Release .

Отметим, что в приведенном коде программа считывания устанавливает в нуль глобальную переменную g_pStmPtr после демаршалинга. Это сделано потому, что объектная ссылка была маршалирована с флагом MSHLFLAGS_NORMAL и может быть демаршалирована только один раз. Во многих сценариях это не является проблемой. В некоторых других сценариях, однако, желательно маршалировать указатель из одного потока и иметь несколько рабочих потоков, в которых можно демаршалировать интерфейсный указатель по мере необходимости. Если все рабочие потоки выполняются в МТА, то это не является проблемой, поскольку нужно выполнить демаршалинг только в одном потоке – от имени всех потоков, выполняющихся в МТА. Если, однако, рабочие потоки выполняются в произвольных апартаментах, то этот подход не сработает, поскольку тогда придется независимо демаршалировать объектную ссылку в каждом рабочем потоке. Большинство разработчиков в этом случае обращаются к флагу MSHLFLAGS_TABLESTRONG, надеясь на однократный маршалинг и столько демаршалингов, сколько необходимо (по одному разу на апартамент). К сожалению, табличный маршалинг (в отличие от обычного маршалинга) не поддерживается в случае, если исходный указатель является заместителем, что случается часто, особенно в распределенных приложениях. Для разрешения этой проблемы в выпуске СОМ Service Pack 3 под Windows NT 4.0 вводится Глобальная интерфейсная таблипа (Global Interface Table – GIT).

вернуться

1 Может показаться странным, что глобальная переменная является интерфейсным указателем, который инициализирован в апартаменте программы записи, а используется из апартамента программы считывания. Об этой противоречивости упоминается в документации по CoMarshalInterThreadInterfaceInStream, где формулируется, что к результирующему интерфейсному указателю IStream можно обратиться из любого апартамента в процессе.