СОМ дает разработчикам возможность группировать родственные СОМ-классы в логические группы, или категории компонентов. Чаще всего все классы внутри категории будут реализовывать один и тот же набор интерфейсов. В то же время простое разделение пространства классов на части по признаку, какие интерфейсы какой класс реализует, не обеспечивает должного уровня модульности для многих приложений. Категории компонентов выступают как метаинформация, показывающая, какие классы совместимы с определенными семантическими ограничениями.
   Категория компонентов есть группа логически родственных СОМ-классов, которые разделяют общий ID категории, или CATID. Идентификаторы категории CATID – это GUID, записанные в реестре как атрибуты класса. Каждый класс может иметь два подключа: Implemented Categories и Required Categories (реализованные категории и нужные категории). Представим, что есть две категории компонентов: Simians и Mammals (приматы и млекопитающие). Каждая из этих двух категорий будет иметь уникальный CATID (CATID_Simians и CATID_Mammals соответственно). Допустим, что класс Chimp является членом каждой из этих категорий, и тогда для Chimp ключ реестра Implemented Categories будет содержать в себе каждый GUID как отдельный подключ:
 
   [HKCR\CLSID\{CLSID_Chimp}\Implemented Categories\{CATID_Mammals}]
   [HKCR\CLSID\{CLSID_Chimp}\Implemented Categories\{CATID_Simians}]
 
   Эти элементы реестра обычно добавляются во время саморегистрации. Каждая известная категория компонентов в системе имеет запись в разделе реестра HKEY_CLASSES_ROOT\Component Categories
   Каждая категория имеет свой собственный уникальный подключ, названный как CATID. Под этим подключом каждая категория имеет одну или более именованных величин, содержащих текстовое описание этой категории. Например, двум показанным выше категориям понадобятся такие элементы реестра:
 
   [HKCR\Component Categories\{CATID_Mammals}] 409="Bears live young"
   [HKCR\Component Categones\{CATID_Simians}] 409="Eats Bananas"
 
   Отметим, что в этом примере используется величина 409, являющаяся кодом локализации, или локальным идентификатором языка LCID (locale identifier), для U.S.English. Другая местная специфика может поддерживаться путем добавления дополнительных именованных величин.
   Классы также могут указать, что они требуют от клиента функциональное назначение определенного типа. Обычно такая поддержка принимает вид узловых интерфейсов (site interfaces), которые клиент предоставляет активированному объекту. Для того, чтобы разделить эти предоставляемые клиентом сервисы на категории, не зависящие от отдельного интерфейса, СОМ позволяет классам объявлять второй тип категорий ID; он может использоваться клиентами для гарантии того, что они не активировали компонент, который не могут должным образом принять. Рассмотрим следующие две категории сервисов, предоставляемых клиентом: CATID_HasOxygen и CATID_HasWater. Поскольку для выживания шимпанзе необходимы кислород и вода, разработчик Chimp должен объявить, что эти две категории сервисов, предоставляемых клиентом, необходимы для активации. Это делается с помощью подключей из Required Categories:
 
   [HKCR\CLSID\{CLSID_Chimp}\Required Categories\{CATID_HasOxygen}]
   [HKCR\CLSID\{CLSID_Chimp}\Required Categories\{CATID_HasWater}]
 
   Кроме того, ID этих двух категорий следует внести в реестр под ключом HKEY_CLASSES_ROOT\Component Categories
   Получив эти записи, сам клиент перед активацией должен убедиться в том, что он удовлетворяет запрошенным категориям. СОМ не обеспечивает согласование с клиентом.
   Элементы категорий компонентов могут быть зарегистрированы либо с помощью явных функций реестра, либо с использованием предлагаемого СОМ менеджера категорий компонентов (component category manager). Этот менеджер категорий компонентов объявляется в СОМ как создаваемый СОМ-класс (CLSID_StdComponentCategoriesMgr), который реализует интерфейс ICatRegister для регистрации информации о категории и интерфейс ICatInformation для запроса информации о категории. Интерфейс ICatRegister позволяет библиотекам DLL сервера легко добавлять в реестр необходимые элементы:
 
   [object, uuid(0002E012-0000-0000-C000-000000000046)]
   interface ICatRegister : IUnknown {
   // description info for a category
   // описательная информация для категории
   typedef struct tagCATEGORYINFO
   { CATID catid; LCID lcid; OLECHAR szDescription[128]; }
   CATEGORYINFO;
   // register cCts category descriptions
   // регистрируем описания категории cCts
   HRESULT RegisterCategories([in] ULONG cCts,
   [in, size_is(cCts)] CATEGORYINFO rgCatInfo[]);
   // unregister cCategories category descriptions
   // отменяем регистрацию описаний категории
   cCategories HRESULT UnRegisterCategories([in] ULONG cCategories,
   [in, size_is(cCategories)] CATID rgcatid[]);
   // indicate a class implements one or more categories
   // показываем, что класс реализует одну или более категорий
   HRESULT RegisterClassImplCategories([in] REFCLSID rclsid,
   [in] ULONG cCategories,
   [in, size_is(cCategories)] CATID rgcatid[]);
   // deindicate a class implements one or more categories
   // перестаем показывать, реализует класс одну или более категорий
   HRESULT UnRegisterClassImplCategories([in] REFCLSID rclsd,
   [in] ULONG cCategories,
   [in, size_is(cCategories)] CATID rgcatid[]);
   // indicate a class requires one or more categories
   // показываем, что класс требует одну или более категорий
   HRESULT RegisterClassReqCategories([in] REFCLSID rclsid,
   [in] ULONG cCategories,
   [in, size_is(cCategories)] CATID rgcatid[]):
   // deindicate a class requires one or more categories
   // перестаем показывать, требует ли класс одну или более категорий
   HRESULT UnRegisterClassReqCategones([in] REFCLSID rclsid,
   [in] ULONG cCategories,
   [in, size_is(cCategories)] CATID rgcatid[]); }
 
   Для определяемых пользователем СОМ-классов нет необходимости реализовывать этот интерфейс. Он существует единственно для того, чтобы серверы смогли сами зарегистрировать свои категории компонентов с использованием реализации предоставляемого СОМ менеджера категорий компонентов.
   В случае примера с Chimp следующий код зарегистрирует правильную информацию о каждой категории:
 
   // get the standard category manager
   // получим стандартный менеджер категорий
   ICatRegister *pcr = 0; HRESULT hr = CoCreateInstance(
   CLSID_StdComponentCategoriesMgr, 0,
   CLSCTX_ALL, IID_ICatRegister, (void**)&pcr); if (SUCCEEDED(hr)) {
   // build descriptions of each category
   // формируем описания каждой категории
   CATECORYINFO rgcc[4];
   rgcc[0].catid = CATID_Simian;
   rgcc[1].catid = CATID_Mammal;
   rgcc[2].catid = CATID_HasOxygen;
   rgcc[3].catid = CATID_HasWater;
   rgcc[0].lcid = rgcc[1].lcid = rgcc[2].lcid = rgcc[3].lcid = 0х409;
   wcscpy(rgcc[0].szDescription, OLESTR(«Eats Bananas»));
   wcscpy(rgcc[1].szDescription, OLESTR(«Bears live young»));
   wcscpy(rgcc[2].szDescription, OLESTR(«Provides Oxygen»));
   wcscpy(rgcc[3].szDescription, OLESTR(«Provides Water»));
   // register information regarding categories
   // регистрируем информацию о категориях
   pcr->RegisterCategories(4, rgcc);
   // note that Chimps are Simians and mammals
   // отметим, что Chimps (шимпанзе) являются Simian
   // (обезьянами) и Mammal (млекопитающими)
   CATID rgcid[2];
   rgcid[0] = CATID_Simian;
   rgcid[1] = CATID_Mammal;
   pcr->RegisterClassImplCategories(CLSID_Chimp, 2, rgcid);
   // note that Chimps require Oxygen and Water
   // отметим, что Chimps (шимпанзе) нуждаются
   // в кислороде (Oxygen) и воде (Water)
   rgcid[0] = CATID_HasOxygen;
   rgcid[1] = CATID_HasWater;
   pcr->RegisterClassReqCategories(CLSID_Chimp, 2, rgcid);
   pcr->Release(); }
 
   Заметим, что в этом коде не делается обычных вызовов реестровых API-функций, а вместо них для обработки реестра используется стандартный менеджер категорий.
   Кроме того, стандартный менеджер категорий позволяет приложениям запрашивать реестр найти информацию о категориях. Эта функциональная возможность предоставляется через интерфейс ICatInformation:
 
   [object, uuid(0002E013-0000-0000-C000-000000000046)]
   interface ICatInformation : IUnknown
   {
   // get list of known categories
   // получаем список известных категорий
   HRESULT EnumCategories([in] LCID lcid, [out] IEnumCATEGORYINFO** ppeci);
   // get description of a particular category
   // получаем описание определенной категории
   HRESULT GetCategoryDesc([in] REFCATID rcatid, [in] LCID lcid, [out] OLECHAR ** ppszDesc);
   // get list of classes compatible with specified categories
   // получаем список классов, совместимых с указанными категориями
   HRESULT EnumClassesOfCategories(
   [in] ULONG cImplemented,
   // -1 indicates ignore
   // (-1) означает игнорировать
   [in,size_is(cImplemented)] CATID rgcatidImpl[], [in] ULONG cRequired,
   // -1 indicates ignore
   // (-1) означает игнорировать
   [in,size_is(cRequired)] CATID rgcatidReq[], [out] IEnumCLSID** ppenumClsid);
   // verify class is compatible with specified categories
   // проверяем, совместим ли класс с указанными категориями
   HRESULT IsClassOfCategories([in] REFCLSID rclsid,
   [in] ULONG cImplemented,
   [in,size_is(cImplemented)] CATID rgcatidImpl[],
   [in] ULONG cRequired,
   [in,size_is(cRequired)] CATID rgcatidReq[]);
   // get list of class's implemented categories
   // получаем список реализованных категорий класса
   HRESULT EnumImplCategoriesOfClass([in] REFCLSID rclsid,
   [out] IEnumCATID** ppenumCatid);
   // get list of class's required categories
   // получаем список категорий, необходимых классу
   HRESULT EnumReqCategoriesOfClass([in] REFCLSID rclsid,
   [out] IEnumCATID** ppenumCatid);
   }
 
   Большинство этих методов возвращают свои курсоры на списки идентификаторов категории или класса. Эти указатели называются нумераторами (enumerators ) и подробно описываются в главе 7.
   Следующий код показывает, как выделить список классов, являющихся членами категории Mammal:
 
   // get the standard category manager // получаем стандартный менеджер категорий
   ICatInformation *pci = 0; HRESULT hr = CoCreateInstance(
   CLSID_StdComponentCategoriesMgr, 0,
   CLSCTX_ALL, IID_ICatInformat1on, (void**)&pci); if (SUCCEEDED(hr)) {
   // get the classes that are Simians (ignore required cats)
   // получаем классы, являющиеся Simian
   // (игнорируем требуемые категории)
   IEnumCLSID *pec = 0;
   CATID rgcid[1];
   rgcid[0] = CATID_Simian;
   hr = pci->EnumClassesOfCategories(1, rgcid, -1, 0, &pec);
   if (SUCCEEDED(hr)) {
   // walk list of CLSIDs 64 at a time
   // просматриваем список CLSID no 64 за проход
   enum { MAX = 64 };
   CLSID rgclsid[MAX];
   do {
   ULONG cActual = 0;
   hr = pec->Next(MAX, rgclsid, &cActual);
   if (SUCCEEDED(hr)) {
   for (ULONG i = 0; i < cActual; i++)
   DisplayClass(rgclsid[i]);
   }
   }
   while (hr == S_OK);
   pec->Release();
   }
   pci->Release(); }
 
   Этот фрагмент кода игнорирует то обстоятельство, что клиентская программа может не поддерживать нужные категории результирующего списка классов. Если бы клиент был осведомлен о том, какие локальные категории им поддерживаются, то он мог бы указать список всех поддерживаемых категорий.
   Рассмотрим следующий вызов EnumClassesOfCategories:
 
   CATID rgimpl[1]; rgimpl[0] = CATID_Simians;
   CATID rgreq[3]; rgreq[0] = CATID_HasWater;
   rgreq[1] = CATID_HasOxygen; rgreq[2] = CATID_HasMilk;
   hr =pci->EnumClassesOfCategories(1, rgimpl, 3, rgreq, &pec);
 
   Результирующий список классов будет содержать всех приматов (Simians), которые не требуют от среды клиента ничего, кроме кислорода (Oxygen), воды (Water) и молока (Milk). Класс Chimp, зарегистрированный ранее, мог бы быть совместимым классом, так как он реализует специфицированную категорию Simian и требует подмножество специфицированных категорий, использованных в запросе.
   Заключительным, причем спорным, аспектом категорий компонентов является представление о классе по умолчанию для категории. СОМ допускает регистрацию CATID в качестве CLSID под ключом реестра HKEY_CLASSES_ROOT\CLSID
   Для преобразования CATID в CLSID по умолчанию используется средство TreatAs , введенное эмуляцией. Для указания того, что класс Gorilla является классом по умолчанию для Simian, необходимо добавить следующий ключ реестра:
 
   [HKCR\CLSID\{CATID_Simian}\TreatAs] @={CLSID_Gorilla}
 
   Это простое соглашение позволяет клиентам просто использовать CATID там, где ожидаются CLSID:
 
   // create an instance of the default Simian class
   // создаем экземпляр класса Simian, принятого по умолчанию
   hr = CoCreateInstance(CATID_Simian, 0, CLSCTX_ALL, IID_IApe, (void**)&pApe);
 
   Если для указанной категории не зарегистрировано ни одного класса по умолчанию, то вызов активации даст сбой и вернет REGDB_E_CLASSNOTREG.
 

Где мы находимся?

   В этой главе представлена концепция СОМ-класса. СОМ-классами называются конкретные типы данных, которые экспортируют один или более интерфейсов и являются основной абстракцией, используемой при активации объектов в СОМ. СОМ поддерживает три примитива активации. CoGetClassObject связывает ссылку с объектом класса, который представляет независимые от экземпляра функциональные возможности класса. CoCreateInstanceEx связывает ссылку с новым экземпляром класса, a CoGetInstanceFromFile связывает ссылку с постоянным экземпляром, находящимся в файле. Моникеры используются в качестве универсальной абстракции для передачи клиентам стратегии связывания и активации, причем MkParseDisplayName выполняет функции точки входа в пространство имен СОМ.
 

Глава 4. Объекты

   class object
   {
   public:
   template <class T> virtual T * dynamic_cast(const type_info& t = typeid(T) )
   };
Аноним, 1995

   В главе 2 обсуждались основы интерфейсов СОМ вообще и интерфейс IUnknown в частности. Было дано понятие о том, что путем наследования дополнительным интерфейсам объекты могут выставлять более одного вида функциональных возможностей. Был также продемонстрирован механизм, с помощью которого клиенты могут опрашивать объекты, чтобы найти среди них доступные функциональные возможности. Этот механизм – QueryInterface (Интерфейс запросов) – был выделен как версия С++-оператора преобразования типа dynamic_cast, не зависящая от языка программирования и от компилятора.
   В предыдущей главе было показано, что QueryInterface можно реализовать непосредственно, используя статические преобразования типа для того, чтобы ограничить область действия указателя this на объект типом интерфейса, который запрашивается клиентом. На физическом уровне этот способ означает просто преобразование идентификаторов интерфейса в объект с помощью соответствующего смещения, то есть способ, который применяется любым компилятором C++ при реализации dynamic_cast.
   Хотя реализации QueryInterface из предыдущей главы являются вполне допустимыми для СОМ, правила IUnknown предоставляют разработчику объектов значительно больше гибкости, чем было показано до сих пор. В данной главе эти правила будут исследованы, и продемонстрированы способы реализации, которые из них вытекают.
 

Снова IUnknown

   IUnknown не имеет реализации по умолчанию, которая являлась бы частью интерфейса системного вызова СОМ. Заголовочные файлы SDK не содержат базовых классов, макросов или шаблонов, предусматривающих реализации QueryInterface, AddRef и Release, которые должны использоваться во всех программах на С или C++. Вместо этого Спецификация СОМ (Component Object Model Specification) предоставляет очень точные правила относительно допущений, которые клиенты и объекты могут делать относительно этих трех методов. Этот набор правил формирует протокол IUnknown и позволяет каждому разработчику объекта преобразовать три указанных метода IUnknown во все, что имеет смысл для его или ее объекта.
   В главе 2 представлены фактические С++-реализации трех упомянутых методов, но СОМ никоим образом не обязывает объекты использовать их. Все, что требует СОМ, – это чтобы каждая реализация придерживалась базовых правил IUnknown. Как это достигается, не имеет ни малейшего отношения к СОМ. Это делает СОМ совершенно ненавязчивой, так как эта модель не требует, чтобы объект делал системные вызовы, наследовал системным реализациям, а все, что от него требуется, – это объявлять совместимые с СОМ указатели vptr. На самом деле, как будет показано далее в этой главе, можно выставлять наследующие IUnknown указатели vptr из классов, которые не наследуют ни одному интерфейсу СОМ.
   Правила IUnknown в совокупности определяют, что значит быть объектом СОМ. Чтобы понять правила IUnknown, полезно начать с конкретного примера. Рассмотрим следующую иерархию интерфейсов:
 
   import «unknwn.idl»;
   [object, uuid(CD538340-A56D-11d0-8C2F-0080C73925BA)]
   interface IVehicle : IUnknown {
   HRESULT GetMaxSpeed([out, retval] long *pMax); }
   [object, uuid(CD53834l-A56D-11d0-8C2F-0080C73925BA)]
   interface ICar : IVehicle {
   HRESULT Brake(void); }
   [object, uuid(CD538342-A56D-11d0-8C2F-0080C73925BA)]
   interface IPlane : IVehicle {
   HRESULT TakeOff(void); }
   [object, uuid(CD538343-A56D-11d0-8C2F-0080C73925BA)]
   interface IBoat : IVehicle {
   HRESULT Sink(void); }
   СОМ использует стандартную технологию для визуального представления объектов. Эта технология находится в рамках принципа СОМ отделения интерфейса от реализации и не раскрывает никаких деталей реализации объекта, кроме списка выставляемых им интерфейсов. Эта технология также визуально усиливает многие из правил IUnknown. Рисунок 4.1 показывает стандартное представление класса CarBoatPlane, который реализует все только что определенные интерфейсы. Заметим, что единственный вывод, который можно сделать из этого рисунка, таков: если не произойдет катастрофического сбоя, объекты CarBoatPlane будут выставлять пять интерфейсов: IBoat, IPlane, ICar, IVehicle и IUnknown.
   Первое правило IUnknown, подлежащее исследованию, – это требование, чтобы QueryInterface был симметричным, транзитивным и рефлексивным (Symmetric/Transitive/Reflexive). Эти требования определяют отношения между всеми интерфейсными указателями объекта и начинают определять понятие идентификации (identity) объектов СОМ. Подобно всем правилам IUnknown, эти требования должны исполняться всегда, за исключением катастрофических сбоев, теми, кто хочет считаться действительным объектом СОМ.
 
 
 

QueryInterface симметрична

   Спецификация СОМ требует, чтобы, если запрос QueryInterface на интерфейс B удовлетворяется через интерфейсный указатель типа A, то запрос QueryInterface на интерфейс A того же самого объекта через результирующий интерфейсный указатель типа В всегда был успешным. Это значит, что если верно QI(A)->B, то также должно быть верным QI(QI(A)->B)->A
   Из свойства, показанного на рис. 4.2, следует, что утверждение, заключенное в следующем коде, всегда должно быть истинным:
 
   void AssertSymmetric(ICar *pCar) { if (pCar)
   {
   IPlane *pPlane = 0;
   // request a second type of interface
   // запрашиваем второй тип интерфейса
   HRESULT hr = pCar->QueryInterface(IID_IPlane, (void**)&pPlane);
   if (SUCCEEDED(hr)) { ICar *pCar2 = 0;
   // request original type of interface
   // запрашиваем исходный тип интерфейса
   hr = pPlane->QueryInterface(IID_ICar, (void**)&pCar2);
   // if the following assertion fails, pCar
   // did not point to a valid СОМ object
   // если следующее утверждение не будет правильным,
   // то pCar не укажет на правильный СОМ-объект
   assert(SUCCEEDED(hr));
   pCar2->Release();
   }
   pPlane->Release();
   }
   }
 
   Симметричность QueryInterface означает, что клиенты не должны заботиться о том, какой из интерфейсов запрашивать первым, так как любые два типа интерфейсов могут быть запрошены в любом порядке.
 

QueryInterface транзитивна

   Спецификация СОМ требует также, чтобы, если запрос QueryInterface на интерфейс В удовлетворяется через интерфейсный указатель типа A, а второй запрос QueryInterface на интерфейс C удовлетворяется через указатель типа В , то запрос QueryInterface на интерфейс C через исходный указатель типа A был бы также успешным. Это означает, что если верно QI(QI(A)->B)->C, то должно быть верным и QI(A)->C
   Это условие иллюстрируется рис. 4.3 и означает, что утверждение, приведенное в нижеследующем коде, должно всегда быть верным:
 
   void AssertTransitive(ICar *pCar)
   {
   if (pCar)
   {
   IPlane *pPlane = 0;
   // request intermediate type of interface
   // запрос промежуточного типа интерфейса
   HRESULT hr = pCar->QueryInterface(IID_IPlane, (void**)&pPlane);
   if (SUCCEEDED(hr))
   {
   IBoat *pBoat1 = 0;
   // request terminal type of interface
   // запрос конечного типа интерфейса
   hr = pPlane->QueryInterface(IID_IBoat, (void**)&pBoat1);
   if (SUCCEEDED(hr))
   {
   IBoat *pBoat2 = 0;
   // request terminal type through the original pointer
   // запрос конечного типа через исходный указатель
   hr = pCar->QueryInterface(IID_IBoat, (void**)&pBoat2);
   // if the following assertion fails, pCar
   // did not point to a valid СОМ object
   // если следующее утверждение неверно, то pCar
   // не указывал на корректный СОМ-объект
   assert(SUCCEEDED(hr));
   pBoat2->Release();
   }
   pBoat1->Release();
   }
   pPlane->Release();
   }
   }
 
 
   Из транзитивности QueryInterface следует, что все интерфейсы, которые выставляет объект, равноправны и не требуют, чтобы их вызывали в какой-то определенной последовательности. Если бы это было не так, то клиентам пришлось бы заботиться о том, какой указатель на объект использовать для различных запросов QueryInterface. Из транзитивности и симметричности QueryInterface следует, что любой интерфейсный указатель на объект выдаст тот же самый ответ «да/нет» на любой запрос QueryInterface. Единственная ситуация, не охватываемая транзитивностью и симметричностью, это повторные запросы одного и того же интерфейса. Эта ситуация требует, чтобы QueryInterface был и рефлективным.
 

QueryInterface рефлективна

   Спецификация СОМ требует, чтобы запрос QueryInterface через интерфейсный указатель всегда достигал цели, если запрошенный тип соответствует типу указателя, с помощью которого произведен запрос. Это означает, что QI(A)->A всегда должен быть верным.
 
 
 
   Это требование проиллюстрировано рис. 4.4 и в следующем фрагменте кода:
 
   void AssertReflexive(ICar *pCar)
   {
   if (pCar)
   {
   ICar *pCar2 = 0;
   // request same type of interface
   // запрос интерфейса того же типа
   HRESULT hr = pCar->QueryInterface(IID_ICar, (void**)&pCar2);
   // if the following assertion fails, pCar
   // did not point to a valid СОМ object
   // если следующее утверждение неверно, то pCar
   // не указывает на корректный объект СОМ
   assert(SUCCEEDED(hr));
   pCar2->Release();
   }
   }
 
   Из этого кода следует, что все реализации ICar должны быть способны удовлетворить дополнительные запросы QueryInterface для ICar через интерфейсный указатель ICar. Если бы это не соблюдалось, то было бы невозможно передавать жестко типизированные интерфейсы через параметры базового типа без невосполнимой потери исходного типа:
 
   extern void GetCar(ICar **ppcar);
   extern void UseVehicle(IVehicle *pv);
   ICar *pCar;
   GetCar(&pCar);
   UseVehicle(pCar);
   // ICar-ness is syntactically lost
   // ICar-ность синтаксически потеряна
   void UseVehicle(IVehicle *pv)
   {
   ICar *pCar = 0;
   // try to regain syntactic ICar-ness
   // пытаемся восстановить синтаксическую ICar-ность
   HRESULT hr = pv->QueryInterface(IID_ICar, (void**)&pCar);
   }
 
   Поскольку указатель, использованный в функции UseVehicle , имеет то же самое значение, что и указатель ICar , переданный вызывающим объектом, то выглядело бы неестественным (counterintuitive), если бы этот тип не мог быть восстановлен внутри функции.
   Из того, что QueryInterface является симметричным, рефлексивным и транзитивным, следует, что любой интерфейсный указатель на объект должен выдавать тот же самый ответ «да/нет» на данный запрос QueryInterface. Это позволяет клиентам рассматривать иерархию типов объекта как простой граф, все вершины которого непосредственно соединены друг с другом (и с самими собой) с помощью открытых (explicit) ребер. На рис. 4.5 изображен такой граф. Отметим, что в любую вершину графа можно попасть из любой другой вершины, пройдя вдоль только одного ребра.
 
 
 
 

Объекты имеют статический тип

   Один из выводов, который можно сделать из трех требований QueryInterfасе , состоит в том, что множество интерфейсов, поддерживаемых объектом, не может изменяться во времени. Спецификация СОМ четко требует, чтобы этот вывод был верен для всех объектов. Из этого требования следует, что иерархия типов объекта является статичной, несмотря на тот факт, что для определения множества поддерживаемых типов данных клиенты должны опрашивать объекты динамически. Если объект отвечает «да» на запрос интерфейса типа А, то он должен отвечать «да», начиная с этой точки. Если объект отвечает «нет» на запрос интерфейса типа А , то он должен отвечать «нет», начиная с этой точки. Фраза «начиная с этой точки» (from that point on) буквально переводится как «до тех пор, пока есть хотя бы один внешний указатель интерфейса на объект». Обычно это соответствует жизненному циклу базового объекта C++, но язык Спецификации СОМ обладает достаточной свободой, чтобы предоставить разработчикам определенную гибкость (например, иерархия типов глобальной переменной может изменяться, когда все указатели освобождены).
   Из того, что все объекты СОМ имеют статическую иерархию типов, следует, что утверждение, записанное в следующем коде, никогда не должно быть ложным, несмотря на то, что идентификатор интерфейса используется в качестве второго параметра:
 
   void AssertStaticType(IUnknown *pUnk, REFIID riid)
   {
   IUnknown *pUnk1 = 0,
   *pUnk2 = 0;
   HRESULT hr1 = pUnk->QueryInterface(riid, (void**)&pUnk1);
   HRESULT hr2 = pUnk->QueryInterface(riid, (void**)&pUnk2);
   // both requests for the same interface should
   // yield the same yes/no answer
   // оба запроса того же самого интерфейса
   // должны получить тот же самый ответ да/нет
   assert(SUCCEEDED(hr1) == SUCCEEDED(hr2));
   if (SUCCEEDED(hr1)) pUnk1->Release();
   if (SUCCEEDED(hr2)) pUnk2->Release();
   }
 
   Это требование означает, что в СОМ запрещены следующие программные технологии:
 
   Использование временной информации при решении вопроса о том, удовлетворять или нет запрос QueryInterface (например, выдавать интерфейс IMorning (утро) только до 12:00).
   Использование переменной информации о состоянии при решении вопроса о том, удовлетворять или нет запрос QueryInterface (например, выдавать интерфейс INotBusy (не занят), только если количество внешних интерфейсных указателей меньше десяти).
   Использование маркера доступа (security token) вызывающего объекта для решения, удовлетворять или нет запрос QueryInterface . Как будет объяснено в главе 6, на самом деле это не обеспечивает никакой реальной безопасности из-за протокола передачи (wire protocol ), используемого СОМ.
   Использование успешного захвата динамических ресурсов для решения вопроса о том, удовлетворять или нет запрос QueryInterface (например, выдавать интерфейс IHaveTonsOfMemory (у меня тонны памяти) только при успешном выполнении malloc(4096*4096)).