Сущность технологии COM

       

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


Хотя фрагменты кода для 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; }



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

Отметим, что в приведенном коде программа считывания устанавливает в нуль глобальную переменную g_pStmPtr после демаршалинга. Это сделано потому, что объектная ссылка была маршалирована с флагом MSHLFLAGS_NORMAL и может быть демаршалирована только один раз. Во многих сценариях это не является проблемой. В некоторых других сценариях, однако, желательно маршалировать указатель из одного потока и иметь несколько рабочих потоков, в которых можно демаршалировать интерфейсный указатель по мере необходимости.


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

GIT является оптимизацией CoMarshalInterface / CoUnmarshalInterface, которая допускает обращение к интерфейсным указателям из всех апартаментов процесса. Внутри СОМ реализуется одна GIT на процесс. GIT содержит маршалированные интерфейсные указатели, которые могут быть эффективно демаршалированы несколько раз внутри одного и того же процесса. Это семантически эквивалентно использованию табличного маршалинга, однако GIT можно использовать как для объектов, так и для заместителей. GIT выставляет интерфейс IGlobalInterfaceTable:

[uuid(00000146-0000-0000-C000-000000000046), object, local ] interface IGlobalInterfaceTable : IUnknown { // marshal an Interface into the GIT // маршалируем интерфейс в GIT HRESULT RegisterInterfaceInGlobal ( [in, iid_is(riid)] IUnknown *pItf, [in] REFIID riid, [out] DWORD *pdwCookie); // destroy the marshaled object reference // уничтожаем маршалированную объектную ссылку HRESULT RevokeInterfaceFromGlobal ( [in] DWORD dwCookle); // unmarshal an interface from the GIT // демаршалируем интерфейс из GIT HRESULT GetInterfaceFromGlobal ( [in] DWORD dwCookie, [in] REFIID riid, [out, iid_is(riid)] void **ppv); }



Клиенты получают доступ к GIT для своего процесса, вызывая CocreateInstance с использованием класса CLSID_StdGlobalInterfaceTable. Каждый вызов CoCreateInstance с применением этого CLSID возвращает указатель на одну и ту же GIT в процессе. Так же как к интерфейсу IStream, возвращенному CoMarshalInterThreadInterfaceInStream, к интерфейсным указателям на GIT можно обратиться из любого апартамента без обязательного маршалинга.

Для того чтобы сделать интерфейсный указатель доступным для всех апартаментов процесса, апартамент, содержащий этот интерфейсный указатель, должен зарегистрировать его в GIT путем вызова метода RegisterInterfaceInGlobal. GIT вернет вызывающей программе DWORD, который представляет глобальный указатель для всех апартаментов процесса. Этот DWORD может быть использован из любого апартамента процесса для демаршалинга нового заместителя путем вызова метода GetInterfaceFromGlobal. Этот же DWORD можно использовать для повторного демаршалинга заместителей до тех пор, пока вызов RevokeInterfaceFromGlobal не объявит глобальный интерфейсный указатель недействительным. Приложения, использующие эту глобальную интерфейсную таблицу (GIT), обычно связывают один интерфейсный указатель на весь процесс при запуске:

IGlobalInterfaceTable *g_pGIT = 0; HRESULT Init0nce(void) { assert(g_pGIT == 0); return CoCreateInstance(CLSID_StdGlobalInterfaceTable, 0, CLSCDX_INPROC_5ERVER, IID_IGlobalInterfaceTable, (void**)&g_pGIT); }

Когда глобальная интерфейсная таблица является доступной, передача интерфейсного указателя в другой апартамент сводится к простой регистрации указателя в глобальной интерфейсной таблице:

HRESULT WritePtrToGlobalVariable(IRacer *pRacer) { // where to write the marshaled ptr // куда записывать маршалированный указатель extern DWORD g_dwCookie; // thread synchronization // синхронизация потока extern HANDLE g_heventWritten; // write marshaled object reference to global variable // записываем маршалированную объектную ссыпку в глобальную переменную HRESULT hr = g_pGIT->RegisterInterfaceInGlobal( pRacer, IID_IRacer, &g_dwCookie); // signal other thread that ptr is now available // сообщаем другому потоку о доступности указателя SetEvent(g_heventWritten); return hr; }



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

HRESULT ReadPtrFromGlobalVariable(IRacer * &rpRacer, bool bLastUnmarshal) { // where to write the marshaled ptr // куда записывать маршалированный указатель extern DWORD g_dwCookie; // thread synchronization // синхронизация потока 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 = g_pGIT->GetInterfaceFromGlobal( g_dwCookie, IID_IRacer, (void**)&rpRacer); // if we are the last to unmarshal, revoke the pointer // если мы поспедние в очереди на демаршапинг, то // аннулируем указатель if (bLastUnmarshal) g_pGIT->RevokeInterfaceFromGlobal(g_dwCookie); return hr; }

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

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


Содержание раздела