Страница:
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).
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, способен демаршалировать более чем один заместитель.
Архитектура стандартного маршалинга
Как уже упоминалось ранее в этой главе, СОМ использует протокол ORPC для всех обращений между апартаментами. Это обстоятельство может представлять интерес с точки зрения архитектуры, но некоторые разработчики желают программировать коммуникационный код низкого уровня. Для того чтобы воспользоваться ORPC-коммуникациями, объектам СОМ не требуется делать ничего, кроме реализации IUnknown, для осуществления межапартаментных обращений по протоколу ORPC. По умолчанию при первом вызове CoMarshalInterface для объекта этот объект опрашивается, желает ли он управлять своими собственными межапартаментными связями. Этот вопрос приходит в форме запроса QueryInterface об интерфейсе IMarshal. Большинство объектов не реализуют интерфейс IMarshal и дают отказ на этот запрос со стороны QueryInterface, показывая тем самым, что их вполне удовлетворяет управление всеми связями самой СОМ посредством вызовов ORPC. Те объекты, которые реализуют интерфейс IMarshal, показывают этим, что ORPC им не подходит и что разработчик объекта предпочитает управлять всеми межапартаментными связями посредством специальных заместителей. Когда объект реализует интерфейс IMarshal, то все ссылки на этот объект будут подвергнуты специальному маршалингу. Специальный маршалинг будет обсуждаться позже в данной главе. Если же объект не реализует интерфейс IMarshal, то все ссылки на этот объект будут маршалированы стандартно. Большинство объектов предпочитают использовать стандартный маршалинг, и поэтому ему уделено основное внимание в данном разделе.
Когда CoMarshalInterface впервые определяет, что объект желает использовать стандартный маршалинг, то создается специальный СОМ-объект под названием администратор загушек (stub manager). Это программный модуль, управляющий всеми интерфейсными заглушками активного объекта.
Администратор заглушек действует как идентификационная единица объекта во всей сети и единственным образом идентифицируется Идентификатором Объектов (Object Identifier – OID), который является идентификатором этого объекта во всех апартаментах. Между администраторами заглушек и идентификационными единицами СОМ-объектов имеется взаимно однозначное соответствие. Каждый администратор заглушек ссылается на ровно один СОМ-объект. Каждый СОМ-объект, использующий стандартный маршалинг, будет иметь ровно один администратор заглушек. Администратор заглушек содержит но крайней мере одну неосвобожденную ссылку на объект, которая удерживает ресурсы объекта в памяти. В этом смысле администратор заглушек является еще одним внутрипроцессным клиентом для объекта. Администратор заглушек следит за числом неосвобожденных внешних ссылок и будет существовать до тех пор, пока где-либо в сети останется хотя бы одна неосвобожденная ссылка. Большинство внешних ссылок являются просто заместителями, хотя промежуточные маршалированные объектные ссылки могут удерживать заглушки, чтобы быть уверенными, что в момент создания первого заместителя объект еще существует. Когда неосвобожденные заместители или ссылки уничтожаются, администратор заглушек извещается об этом и декрементирует свой счетчик внешних ссылок. Если уничтожена последняя внешняя ссылка на администратор заглушек, то последний самоуничтожается, освобождая свои неосвобожденные ссылки на действующий объект. Это имитирует эффект наличия на стороне клиента ссылок, поддерживающих объект. Методы явного контроля за временем жизни заглушки будут обсуждаться далее в этой главе.
Администратор заглушек действует лишь как сетевой идентификатор объекта и не понимает, как обрабатывать поступающие ORPC-запросы, предназначенные для объекта[1]. Для того чтобы преобразовать поступающие ORPC-запросы в действительные вызовы методов объекта, администратору заглушек нужен вспомогательный объект, который знает детали сигнатур интерфейсных методов. Этот вспомогательный объект называется интерфейсной заглушкой (interface stub). Он должен правильно демаршалировать параметры [in], которые присутствуют в блоке ORPC-запроса, вызвать метод в действующий объект и затем маршалировать HRESULT и любые параметры [out] в ответный блок ORPC. Интерфейсные заглушки идентифицируются внутри апартамента с помощью Идентификаторов Интерфейсных Указателей (Interface Pointer Identifiers – IPIDs), которые внутри апартамента являются уникальными. Подобно администратору заглушек, каждая интерфейсная заглушка содержит ссылку на объект. Однако поддерживаемый интерфейс будет интерфейсом определенного типа, а не просто IUnknown. На рис. 5.3 показана взаимозависимость между администратором заглушек, интерфейсными заглушками и объектом. Отметим, что некоторые интерфейсные заглушки знают, как декодировать более чем один интерфейсный тип, в то время как другие понимают только один интерфейс.
Когда CoUnmarshalInterface демаршалирует стандартно маршалированную объектную ссылку, фактически эта функция возвращает указатель администратору заместителей (proxy manager). Этот администратор заместителей действует как копия объекта со стороны клиента и, подобно администратору заглушек, не имеет никакой априорной информации ни об одним из интерфейсов СОМ. Однако администратор заместителей знает, как реализовать три метода IUnknown. Любые дополнительные вызовы AddRef или Release просто увеличивают или уменьшают на единицу внутренний счетчик ссылок в администраторе заместителей и никогда не передаются с использованием ORPC. Последний Release в администраторе заместителей уничтожает заместитель, посылая в апартамент объекта требование о прекращении связи. Запросы QueryInterface в администраторе заместителей обрабатываются несколько иначе. Подобно администратору заглушек, администратор заместителей не имеет никакой априорной информации об интерфейсах СОМ. Вместо этого администратор заместителей должен загружать интерфейсные заместители, выставляющие тот интерфейс, на который в данный момент идет запрос. Интерфейсный заместитель преобразует вызовы метода в запросы ORPC. В отличие от администратора заглушек, администратор заместителей является непосредственно видимым для программистов, и для обеспечения правильных отношений идентификации интерфейсные заместители агрегируются в идентификационную единицу администратора заместителей. Это создает у клиента иллюзию, что все интерфейсы выставляются одним СОМ-объектом. На рис. 5.4 показаны отношения между администратором заместителей, интерфейсными заместителями и заглушкой.
Как показано на рис. 5.4, заместитель связывается с заглушкой через третий объект, называемый каналом. Канал – это поддерживаемая СОМ обертка вокруг слоя RPC на этапе выполнения. Канал выставляет интерфейс IRpcChannelBuffer
[ uuid(D5F56B60-593B-101A-B569-08002B2DBF7A), local, object ]
interface IRpcChannelBuffer : IUnknown {
// programmatic representation of ORPC message
// программное представление сообщения ORPC
typedef struct tagRPCOLEMESSAGE {
void *reserved1;
unsigned long dataRepresentation;
// endian/ebcdic
// endian /расширенный двоично-десятичный код
// для обмена информацией
void *Buffer;
// payload goes here
// полезная нагрузка идет сюда
ULONG cbBuffer;
// length of payload
// длина полезной нагрузки
ULONG iMethod;
// which method?
// чей метод?
void *reserved2[5];
ULONG rpcFlags;
} RPCOLEMESSAGE;
// allocate a transmission buffer
// выделяем буфер для передачи
HRESULT GetBuffer([inl RPCOLEMESSAGE *pMessage,
[in] REFIID riid);
// send an ORPC request and receive an ORPC response
// посылаем ORPC-запрос и получаем ORPC-ответ
HRESULT SendReceive([in,out] RPCOLEMESSAGE *pMessage,
[out] ULONG *pStatus);
// deallocate a transmission buffer
// освобождаем буфер передачи
HRESULT FreeBuffer([in] RPCOLEMESSAGE *pMessage);
// get distance to destination for CoMarshalInterface
// получаем расстояние до адресата для CoMarshalInterface
HRESULT GetDestCtx([out] DWORD *pdwDestCtx,
[out] void **ppvDestCtx);
// check for explicit disconnects
// проверяем явные отсоединения
HRESULT IsConnected(void);
}
Интерфейсные заместители используют метод SendReceive этого интерфейса, чтобы заставить канал послать блок запросов ORPC и получить блок ответов ORPC.
Интерфейсные заместители и заглушки являются обычными внутрипроцессными объектами СОМ, которые создаются администраторами соответственно заместителей и заглушек с использованием обычной СОМ-технологии активизации. Интерфейсная заглушка должна выставить интерфейс IRpcStubBuffer:
[ uuid(D5F56AFC-593B-101A-B569-08002B2DBF7A), local, object ]
interface IRpcStubBuffer : IUnknown {
// called to connect stub to object
// вызван для соединения заглушки с объектом
HRESULT Connect([in] IUnknown *pUnkServer),
// called to inform stub to release object
// вызван для информирования заглушки об освобождении объекта
void Disconnect(void);
// called when ORPC request arrives
// вызывается, когда поступает запрос ORPC
HRESULT Invoke ([in] RPCOLEMESSAGE *pmsg,
[in] IRpcChannelBuffer *pChannel);
// used to support multiple itf types per stub
// используется для поддержки нескольких типов интерфейсов
// для одной заглушки
IRpcStubBuffer *IsIIDSupported([in] REFIID riid);
// used to support multiple itf types per stub
// используется для поддержки нескольких интерфейсов
// для одной заглушки
ULONG CountRefs(vold);
// used by ORPC debugger to find pointer to object
// используется отладчиком ORPC для поиска указателя на объект
HRESULT DebugServerQueryInterface(void **ppv);
// used by ORPC debugger to release pointer to object
// используется отладчиком ORPC для освобождения указателя на объект
void DebugServerRelease(void *pv);
}
Метод Invoke будет вызываться библиотекой СОМ, когда поступит запрос ORPC на объект. При вводе маршалированные [in]-параметры будут находиться в RPCOLEMESSAGE, а при выводе заглушка должна маршалировать HRESULT метода и любые [out]-параметры, которые будут возвращены в блоке ответов ORPC.
Интерфейсный заместитель должен выставлять интерфейс (интерфейсы), за удаленный доступ к которым он отвечает, в дополнение к интерфейсу IRpcProxyBuffer:
[ uuid(D5F56A34-593B-101A-B569-08002B2DBF7A), local, object ]
interface IRpcProxyBuffer : IUnknown {
HRESULT Connect([in] IRpcChannelBuffer *pChannelBuffer);
void Disconnect(void);
}
Интерфейс IRpcPгoxуBuffer должен быть неделегирующим интерфейсом IUnknown интерфейсного заместителя. Все остальные интерфейсы, которые выставляет интерфейсный заместитель, должны делегировать администратору заместителей свои методы IUnknown. Именно в реализациях метода этих других интерфейсов интерфейсный заместитель должен использовать канал для посылки запросов ORPC на метод интерфейсной заглушки Invoke, который затем обрабатывает этот метод в апартаменте объекта.
Интерфейсные заместители и интерфейсные заглушки динамически связываются и совместно используют единый CLSID как для заместителя, так и для заглушки. Такую раздвоенную реализацию часто называют интерфейсным маршалером (interface marshaler). Объект класса интерфейсного маршалера выставляет интерфейс IPSFactoryBuffer:
[ uuid(D5F569DO-593B-101A-B569-08002B2DBF7A), local, object ]
interface IPSFactoryBuffer : IUnknown {
HRESULT CreateProxy(
[in] IUnknown *pUnkOuter,
// ptr to proxy manager
// указатель на администратор заместителей
[in] REFIID riid,
// the requested itf to remote
// запрошенный интерфейс для удаленного доступа
[out] IRpcProxyBuffer **ppProxy,
// ptr. to proxy itf.
// указатель на интерфейс заместителя
[out] void **ppv
// ptr to remoting interface
// указатель на удаленный интерфейс
);
HRESULT CreateStub(
[in] REFIID riid,
// the requested itf to remote
// запрошенный интерфейс для удаленного доступа
[in] IUnknown *pUnkServer,
// ptr to actual object
// указатель на действующий объект
[out] IRpcStubBuffer **ppStub
// ptr to stub on output
// указатель на заглушку на выходе
);
}
Администратор заместителей вызывает метод CreateProxy с целью агрегирования нового интерфейсного заместителя. Администратор заглушек вызывает метод CreateStub с целью создания новой интерфейсной заглушки.
Когда в объекте запрашивается новый интерфейс, то администраторы заместителей и заглушек должны преобразовать запрошенные IID и CLSID интерфейсного маршалера. Под Windows NT 5.0 хранилише класса совершает эти преобразования в директории NT, и они кэшируются в локальном реестре каждой хост-машины. Отображения IID в CLSID всей машины кэшируются в
HKEY_CLASSES_ROOT\Interface
а отображения каждого пользователя кэшируются в
HKEY_CURRENT_USER\Software\Classes\Interface
Один из этих ключей или оба будут содержать подключи для каждого известного интерфейса. Под Windows NT 4.0 и в более ранних версиях не существует хранилища классов и используется только область локального реестра HKEY_CLASSES_ROOT\Interface.
Если в интерфейсе установлен интерфейсный маршалер, то в реестре будет дополнительный подключ (ProxyStubClsid32). который показывает CLSID интерфейсного маршалера. Ниже показаны необходимые ключи реестра для интерфейсного маршалера:
[HKCR\Interface\{1A3A29F0-D87E-11d0-8C4F-0080C73925BA}]
@="IRacer"
[HKCR\Interface\{1A3A29F0-D87E-11d0-8C4F-OB80C73925BA}\ProxyStubClsid32]
@="{1A3A29F3-D87E-lld0-8C4F-0080C73925BA}"
Эти элементы реестра означают, что существует внутрипроцессный сервер с CLSID, равным {1A3A29F3-D87E-11d0-8C4F-0080C73925BA}, который реализует интерфейсные заместитель и заглушку для интерфейса IRacer ({1A3A29F0-D87E-11d0-8C4F-0080C73925BA}). Из этого следует, что HKCR\CLSID будет иметь подключ для интерфейсного маршалера, отображающего CLSID в соответствующее имя файла DLL. Опять же под Windows NT 5.0 это отображение может содержаться в хранилище классов, которое способно динамически заполнять локальный реестр. Поскольку интерфейсный маршалер должен выполняться в том же апартаменте, что и администратор заместителей или администратор заглушек, они должны использовать (флаг) ThreadingModel="Both" для гарантии того, что они всегда могут загрузиться в нужный апартамент.
Когда CoMarshalInterface впервые определяет, что объект желает использовать стандартный маршалинг, то создается специальный СОМ-объект под названием администратор загушек (stub manager). Это программный модуль, управляющий всеми интерфейсными заглушками активного объекта.
Администратор заглушек действует как идентификационная единица объекта во всей сети и единственным образом идентифицируется Идентификатором Объектов (Object Identifier – OID), который является идентификатором этого объекта во всех апартаментах. Между администраторами заглушек и идентификационными единицами СОМ-объектов имеется взаимно однозначное соответствие. Каждый администратор заглушек ссылается на ровно один СОМ-объект. Каждый СОМ-объект, использующий стандартный маршалинг, будет иметь ровно один администратор заглушек. Администратор заглушек содержит но крайней мере одну неосвобожденную ссылку на объект, которая удерживает ресурсы объекта в памяти. В этом смысле администратор заглушек является еще одним внутрипроцессным клиентом для объекта. Администратор заглушек следит за числом неосвобожденных внешних ссылок и будет существовать до тех пор, пока где-либо в сети останется хотя бы одна неосвобожденная ссылка. Большинство внешних ссылок являются просто заместителями, хотя промежуточные маршалированные объектные ссылки могут удерживать заглушки, чтобы быть уверенными, что в момент создания первого заместителя объект еще существует. Когда неосвобожденные заместители или ссылки уничтожаются, администратор заглушек извещается об этом и декрементирует свой счетчик внешних ссылок. Если уничтожена последняя внешняя ссылка на администратор заглушек, то последний самоуничтожается, освобождая свои неосвобожденные ссылки на действующий объект. Это имитирует эффект наличия на стороне клиента ссылок, поддерживающих объект. Методы явного контроля за временем жизни заглушки будут обсуждаться далее в этой главе.
Администратор заглушек действует лишь как сетевой идентификатор объекта и не понимает, как обрабатывать поступающие ORPC-запросы, предназначенные для объекта[1]. Для того чтобы преобразовать поступающие ORPC-запросы в действительные вызовы методов объекта, администратору заглушек нужен вспомогательный объект, который знает детали сигнатур интерфейсных методов. Этот вспомогательный объект называется интерфейсной заглушкой (interface stub). Он должен правильно демаршалировать параметры [in], которые присутствуют в блоке ORPC-запроса, вызвать метод в действующий объект и затем маршалировать HRESULT и любые параметры [out] в ответный блок ORPC. Интерфейсные заглушки идентифицируются внутри апартамента с помощью Идентификаторов Интерфейсных Указателей (Interface Pointer Identifiers – IPIDs), которые внутри апартамента являются уникальными. Подобно администратору заглушек, каждая интерфейсная заглушка содержит ссылку на объект. Однако поддерживаемый интерфейс будет интерфейсом определенного типа, а не просто IUnknown. На рис. 5.3 показана взаимозависимость между администратором заглушек, интерфейсными заглушками и объектом. Отметим, что некоторые интерфейсные заглушки знают, как декодировать более чем один интерфейсный тип, в то время как другие понимают только один интерфейс.
Когда CoUnmarshalInterface демаршалирует стандартно маршалированную объектную ссылку, фактически эта функция возвращает указатель администратору заместителей (proxy manager). Этот администратор заместителей действует как копия объекта со стороны клиента и, подобно администратору заглушек, не имеет никакой априорной информации ни об одним из интерфейсов СОМ. Однако администратор заместителей знает, как реализовать три метода IUnknown. Любые дополнительные вызовы AddRef или Release просто увеличивают или уменьшают на единицу внутренний счетчик ссылок в администраторе заместителей и никогда не передаются с использованием ORPC. Последний Release в администраторе заместителей уничтожает заместитель, посылая в апартамент объекта требование о прекращении связи. Запросы QueryInterface в администраторе заместителей обрабатываются несколько иначе. Подобно администратору заглушек, администратор заместителей не имеет никакой априорной информации об интерфейсах СОМ. Вместо этого администратор заместителей должен загружать интерфейсные заместители, выставляющие тот интерфейс, на который в данный момент идет запрос. Интерфейсный заместитель преобразует вызовы метода в запросы ORPC. В отличие от администратора заглушек, администратор заместителей является непосредственно видимым для программистов, и для обеспечения правильных отношений идентификации интерфейсные заместители агрегируются в идентификационную единицу администратора заместителей. Это создает у клиента иллюзию, что все интерфейсы выставляются одним СОМ-объектом. На рис. 5.4 показаны отношения между администратором заместителей, интерфейсными заместителями и заглушкой.
Как показано на рис. 5.4, заместитель связывается с заглушкой через третий объект, называемый каналом. Канал – это поддерживаемая СОМ обертка вокруг слоя RPC на этапе выполнения. Канал выставляет интерфейс IRpcChannelBuffer
[ uuid(D5F56B60-593B-101A-B569-08002B2DBF7A), local, object ]
interface IRpcChannelBuffer : IUnknown {
// programmatic representation of ORPC message
// программное представление сообщения ORPC
typedef struct tagRPCOLEMESSAGE {
void *reserved1;
unsigned long dataRepresentation;
// endian/ebcdic
// endian /расширенный двоично-десятичный код
// для обмена информацией
void *Buffer;
// payload goes here
// полезная нагрузка идет сюда
ULONG cbBuffer;
// length of payload
// длина полезной нагрузки
ULONG iMethod;
// which method?
// чей метод?
void *reserved2[5];
ULONG rpcFlags;
} RPCOLEMESSAGE;
// allocate a transmission buffer
// выделяем буфер для передачи
HRESULT GetBuffer([inl RPCOLEMESSAGE *pMessage,
[in] REFIID riid);
// send an ORPC request and receive an ORPC response
// посылаем ORPC-запрос и получаем ORPC-ответ
HRESULT SendReceive([in,out] RPCOLEMESSAGE *pMessage,
[out] ULONG *pStatus);
// deallocate a transmission buffer
// освобождаем буфер передачи
HRESULT FreeBuffer([in] RPCOLEMESSAGE *pMessage);
// get distance to destination for CoMarshalInterface
// получаем расстояние до адресата для CoMarshalInterface
HRESULT GetDestCtx([out] DWORD *pdwDestCtx,
[out] void **ppvDestCtx);
// check for explicit disconnects
// проверяем явные отсоединения
HRESULT IsConnected(void);
}
Интерфейсные заместители используют метод SendReceive этого интерфейса, чтобы заставить канал послать блок запросов ORPC и получить блок ответов ORPC.
Интерфейсные заместители и заглушки являются обычными внутрипроцессными объектами СОМ, которые создаются администраторами соответственно заместителей и заглушек с использованием обычной СОМ-технологии активизации. Интерфейсная заглушка должна выставить интерфейс IRpcStubBuffer:
[ uuid(D5F56AFC-593B-101A-B569-08002B2DBF7A), local, object ]
interface IRpcStubBuffer : IUnknown {
// called to connect stub to object
// вызван для соединения заглушки с объектом
HRESULT Connect([in] IUnknown *pUnkServer),
// called to inform stub to release object
// вызван для информирования заглушки об освобождении объекта
void Disconnect(void);
// called when ORPC request arrives
// вызывается, когда поступает запрос ORPC
HRESULT Invoke ([in] RPCOLEMESSAGE *pmsg,
[in] IRpcChannelBuffer *pChannel);
// used to support multiple itf types per stub
// используется для поддержки нескольких типов интерфейсов
// для одной заглушки
IRpcStubBuffer *IsIIDSupported([in] REFIID riid);
// used to support multiple itf types per stub
// используется для поддержки нескольких интерфейсов
// для одной заглушки
ULONG CountRefs(vold);
// used by ORPC debugger to find pointer to object
// используется отладчиком ORPC для поиска указателя на объект
HRESULT DebugServerQueryInterface(void **ppv);
// used by ORPC debugger to release pointer to object
// используется отладчиком ORPC для освобождения указателя на объект
void DebugServerRelease(void *pv);
}
Метод Invoke будет вызываться библиотекой СОМ, когда поступит запрос ORPC на объект. При вводе маршалированные [in]-параметры будут находиться в RPCOLEMESSAGE, а при выводе заглушка должна маршалировать HRESULT метода и любые [out]-параметры, которые будут возвращены в блоке ответов ORPC.
Интерфейсный заместитель должен выставлять интерфейс (интерфейсы), за удаленный доступ к которым он отвечает, в дополнение к интерфейсу IRpcProxyBuffer:
[ uuid(D5F56A34-593B-101A-B569-08002B2DBF7A), local, object ]
interface IRpcProxyBuffer : IUnknown {
HRESULT Connect([in] IRpcChannelBuffer *pChannelBuffer);
void Disconnect(void);
}
Интерфейс IRpcPгoxуBuffer должен быть неделегирующим интерфейсом IUnknown интерфейсного заместителя. Все остальные интерфейсы, которые выставляет интерфейсный заместитель, должны делегировать администратору заместителей свои методы IUnknown. Именно в реализациях метода этих других интерфейсов интерфейсный заместитель должен использовать канал для посылки запросов ORPC на метод интерфейсной заглушки Invoke, который затем обрабатывает этот метод в апартаменте объекта.
Интерфейсные заместители и интерфейсные заглушки динамически связываются и совместно используют единый CLSID как для заместителя, так и для заглушки. Такую раздвоенную реализацию часто называют интерфейсным маршалером (interface marshaler). Объект класса интерфейсного маршалера выставляет интерфейс IPSFactoryBuffer:
[ uuid(D5F569DO-593B-101A-B569-08002B2DBF7A), local, object ]
interface IPSFactoryBuffer : IUnknown {
HRESULT CreateProxy(
[in] IUnknown *pUnkOuter,
// ptr to proxy manager
// указатель на администратор заместителей
[in] REFIID riid,
// the requested itf to remote
// запрошенный интерфейс для удаленного доступа
[out] IRpcProxyBuffer **ppProxy,
// ptr. to proxy itf.
// указатель на интерфейс заместителя
[out] void **ppv
// ptr to remoting interface
// указатель на удаленный интерфейс
);
HRESULT CreateStub(
[in] REFIID riid,
// the requested itf to remote
// запрошенный интерфейс для удаленного доступа
[in] IUnknown *pUnkServer,
// ptr to actual object
// указатель на действующий объект
[out] IRpcStubBuffer **ppStub
// ptr to stub on output
// указатель на заглушку на выходе
);
}
Администратор заместителей вызывает метод CreateProxy с целью агрегирования нового интерфейсного заместителя. Администратор заглушек вызывает метод CreateStub с целью создания новой интерфейсной заглушки.
Когда в объекте запрашивается новый интерфейс, то администраторы заместителей и заглушек должны преобразовать запрошенные IID и CLSID интерфейсного маршалера. Под Windows NT 5.0 хранилише класса совершает эти преобразования в директории NT, и они кэшируются в локальном реестре каждой хост-машины. Отображения IID в CLSID всей машины кэшируются в
HKEY_CLASSES_ROOT\Interface
а отображения каждого пользователя кэшируются в
HKEY_CURRENT_USER\Software\Classes\Interface
Один из этих ключей или оба будут содержать подключи для каждого известного интерфейса. Под Windows NT 4.0 и в более ранних версиях не существует хранилища классов и используется только область локального реестра HKEY_CLASSES_ROOT\Interface.
Если в интерфейсе установлен интерфейсный маршалер, то в реестре будет дополнительный подключ (ProxyStubClsid32). который показывает CLSID интерфейсного маршалера. Ниже показаны необходимые ключи реестра для интерфейсного маршалера:
[HKCR\Interface\{1A3A29F0-D87E-11d0-8C4F-0080C73925BA}]
@="IRacer"
[HKCR\Interface\{1A3A29F0-D87E-11d0-8C4F-OB80C73925BA}\ProxyStubClsid32]
@="{1A3A29F3-D87E-lld0-8C4F-0080C73925BA}"
Эти элементы реестра означают, что существует внутрипроцессный сервер с CLSID, равным {1A3A29F3-D87E-11d0-8C4F-0080C73925BA}, который реализует интерфейсные заместитель и заглушку для интерфейса IRacer ({1A3A29F0-D87E-11d0-8C4F-0080C73925BA}). Из этого следует, что HKCR\CLSID будет иметь подключ для интерфейсного маршалера, отображающего CLSID в соответствующее имя файла DLL. Опять же под Windows NT 5.0 это отображение может содержаться в хранилище классов, которое способно динамически заполнять локальный реестр. Поскольку интерфейсный маршалер должен выполняться в том же апартаменте, что и администратор заместителей или администратор заглушек, они должны использовать (флаг) ThreadingModel="Both" для гарантии того, что они всегда могут загрузиться в нужный апартамент.
Реализация интерфейсных маршалеров
В предыдущем разделе было показано четыре интерфейса, используемых архитектурой стандартного маршалинга. Хотя и допустимо реализовать интерфейсные маршалеры с помощью ручного кодирования на C++, на практике это осуществляется редко. Дело в том, что компилятор IDL может автоматически генерировать исходный С-код для интерфейсного маршалера на основе IDL-определения интерфейса. Созданные MIDL интерфейсные маршалеры преобразуют параметры метода в последовательную форму, используя протокол Сетевого Представления Данных (Network Data Representation – NDR), который допускает демаршалинг этих параметров при различных архитектурах хост-машин. NDR учитывает различия в порядке следования байтов, в формате с плавающей точкой, в наборе символов и в расположении результатов. NDR поддерживает фактически все совместимые с C типы данных. Для того чтобы обеспечить передачу интерфейсных указателей как параметров, MIDL генерирует вызовы CoMarshalInterface / CoUnmarshalInterface для маршалинга любых параметров интерфейсных указателей. Если параметр является статически типизированным интерфейсным указателем:
HRESULT Method([out] IRacer **ppRacer);
то сгенерированный код маршалера будет маршалировать параметр ppRacer путем передачи IID IRacer (IID_IRacer) вызовам CoMarshalInterface / CoUnmarshalInterface . Если же интерфейсный указатель типизирован динамически:
HRESULT Method([in] REFIID riid, [out, iid_is(riid)] void **ppv);
то сгенерированный код маршалера будет маршалировать интерфейс, используя IID, переданный динамически в первый параметр метода.
MIDL генерирует исходный код интерфейсного маршалера для каждого нелокального интерфейса, определенного вне области действия оператора library . В следующем псевдо-IDL коде
// sports.idl
// виды спорта. Язык описания интерфейсов
[local, object] interface IBoxer : IUnknown { … }
[object] interface IRacer : IUnknown { … }
[object] interface ISwimmer : IUnknown { … }
[helpstring(«Sports Lib»)]
library SportsLibrary {
interface IRacer;
// include def. of IRacer in TLB
// включаем определение IRacer в библиотеку типов TLB
[object] interface IWrestler : IUnknown { … } }
только интерфейсы IRacer и ISwimmer будут иметь исходный код интерфейсного маршалера. MIDL не будет генерировать маршалирующий код для IBoxer, поскольку атрибут [local] подавляет маршалинг. MIDL также не будет генерировать маршалер для IWrestler, поскольку он определен внутри области действия библиотечного оператора.
Если MIDL представлен в IDL такого типа, он будет генерировать пять файлов. Файл sports.h будет содержать С/С++-определения интерфейсов, sports_i.с – IID и LIBID, a sports.tlb – разобранный (tokenized) IDL для IRacer и IWrestler , который можно использовать в средах разработки, поддерживающих СОМ. Файл sports_p.c будет содержать фактические реализации методов интерфейсных заместителей и заглушек, которые осуществляют преобразования вызова методов в NDR. Этот файл также может содержать С-определения виртуальной таблицы (vtable) для интерфейсных заместителей и заглушек наряду со специальным управляющим кодом MIDL. Поскольку интерфейсные маршалеры являются внутрипроцессными серверами СОМ, то четыре стандартные точки входа (DllGetClassObject и другие) должны быть также определены. Эти четыре метода определены в пятом файле dlldata.c.
HRESULT Method([out] IRacer **ppRacer);
то сгенерированный код маршалера будет маршалировать параметр ppRacer путем передачи IID IRacer (IID_IRacer) вызовам CoMarshalInterface / CoUnmarshalInterface . Если же интерфейсный указатель типизирован динамически:
HRESULT Method([in] REFIID riid, [out, iid_is(riid)] void **ppv);
то сгенерированный код маршалера будет маршалировать интерфейс, используя IID, переданный динамически в первый параметр метода.
MIDL генерирует исходный код интерфейсного маршалера для каждого нелокального интерфейса, определенного вне области действия оператора library . В следующем псевдо-IDL коде
// sports.idl
// виды спорта. Язык описания интерфейсов
[local, object] interface IBoxer : IUnknown { … }
[object] interface IRacer : IUnknown { … }
[object] interface ISwimmer : IUnknown { … }
[helpstring(«Sports Lib»)]
library SportsLibrary {
interface IRacer;
// include def. of IRacer in TLB
// включаем определение IRacer в библиотеку типов TLB
[object] interface IWrestler : IUnknown { … } }
только интерфейсы IRacer и ISwimmer будут иметь исходный код интерфейсного маршалера. MIDL не будет генерировать маршалирующий код для IBoxer, поскольку атрибут [local] подавляет маршалинг. MIDL также не будет генерировать маршалер для IWrestler, поскольку он определен внутри области действия библиотечного оператора.
Если MIDL представлен в IDL такого типа, он будет генерировать пять файлов. Файл sports.h будет содержать С/С++-определения интерфейсов, sports_i.с – IID и LIBID, a sports.tlb – разобранный (tokenized) IDL для IRacer и IWrestler , который можно использовать в средах разработки, поддерживающих СОМ. Файл sports_p.c будет содержать фактические реализации методов интерфейсных заместителей и заглушек, которые осуществляют преобразования вызова методов в NDR. Этот файл также может содержать С-определения виртуальной таблицы (vtable) для интерфейсных заместителей и заглушек наряду со специальным управляющим кодом MIDL. Поскольку интерфейсные маршалеры являются внутрипроцессными серверами СОМ, то четыре стандартные точки входа (DllGetClassObject и другие) должны быть также определены. Эти четыре метода определены в пятом файле dlldata.c.