Архитектура стандартного маршалинга
Как уже упоминалось ранее в этой главе, СОМ использует протокол ORPC для всех обращений между апартаментами. Это обстоятельство может представлять интерес с точки зрения архитектуры, но некоторые разработчики желают программировать коммуникационный код низкого уровня. Для того чтобы воспользоваться ORPC-коммуникациями, объектам СОМ не требуется делать ничего, кроме реализации IUnknown, для осуществления межапартаментных обращений по протоколу ORPC. По умолчанию при первом вызове CoMarshalInterface для объекта этот объект опрашивается, желает ли он управлять своими собственными межапартаментными связями. Этот вопрос приходит в форме запроса QueryInterface об интерфейсе IMarshal. Большинство объектов не реализуют интерфейс IMarshal и дают отказ на этот запрос со стороны QueryInterface, показывая тем самым, что их вполне удовлетворяет управление всеми связями самой СОМ посредством вызовов ORPC. Те объекты, которые реализуют интерфейс IMarshal, показывают этим, что ORPC им не подходит и что разработчик объекта предпочитает управлять всеми межапартаментными связями посредством специальных заместителей. Когда объект реализует интерфейс IMarshal, то все ссылки на этот объект будут подвергнуты специальному маршалингу. Специальный маршалинг будет обсуждаться позже в данной главе. Если же объект не реализует интерфейс IMarshal, то все ссылки на этот объект будут маршалированы стандартно. Большинство объектов предпочитают использовать стандартный маршалинг, и поэтому ему уделено основное внимание в данном разделе.
Когда CoMarshalInterface впервые определяет, что объект желает использовать стандартный маршалинг, то создается специальный СОМ-объект под названием администратор загушек (stub manager). Это программный модуль, управляющий всеми интерфейсными заглушками активного объекта.
Администратор заглушек действует как идентификационная единица объекта во всей сети и единственным образом идентифицируется Идентификатором Объектов (Object Identifier — OID), который является идентификатором этого объекта во всех апартаментах.
Между администраторами заглушек и идентификационными единицами СОМ- объектов имеется взаимно однозначное соответствие. Каждый администратор заглушек ссылается на ровно один СОМ-объект. Каждый СОМ-объект, использующий стандартный маршалинг, будет иметь ровно один администратор заглушек. Администратор заглушек содержит но крайней мере одну неосвобожденную ссылку на объект, которая удерживает ресурсы объекта в памяти. В этом смысле администратор заглушек является еще одним внутрипроцессным клиентом для объекта. Администратор заглушек следит за числом неосвобожденных внешних ссылок и будет существовать до тех пор, пока где-либо в сети останется хотя бы одна неосвобожденная ссылка. Большинство внешних ссылок являются просто заместителями, хотя промежуточные маршалированные объектные ссылки могут удерживать заглушки, чтобы быть уверенными, что в момент создания первого заместителя объект еще существует. Когда неосвобожденные заместители или ссылки уничтожаются, администратор заглушек извещается об этом и декрементирует свой счетчик внешних ссылок. Если уничтожена последняя внешняя ссылка на администратор заглушек, то последний самоуничтожается, освобождая свои неосвобожденные ссылки на действующий объект. Это имитирует эффект наличия на стороне клиента ссылок, поддерживающих объект. Методы явного контроля за временем жизни заглушки будут обсуждаться далее в этой главе.
Администратор заглушек действует лишь как сетевой идентификатор объекта и не понимает, как обрабатывать поступающие ORPC-запросы, предназначенные для объекта. Для того чтобы преобразовать поступающие 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" для гарантии того, что они всегда могут загрузиться в нужный апартамент.
1
Логически администратор заглушек обрабатывает удаленные вызоны методов IUnknown. Фактически, однако, эта задача выполняется тем объектом апартамента, который выставляет интерфейс IRemUnknown.