Использование второго CLSID гарантирует, что клиенты не получат случайно старую версию класса Chimp . В то же время старые клиенты делают запросы на активацию с применением старого CLSID:
// old client – старый клиент
IАре *рАре = 0;
hr = CoCreateInstance(CLSID_Chimp, 0, CLSCTX_ALL, IID_Ape, (void**)&pApe);
Чтобы продолжать поддержку старых клиентов, разработчику Chimp необходимо сохранить в реестре исходный CLSID для удовлетворения этих запросов на активацию. Если изменилась семантика класса, то необходимо, чтобы исходный сервер также оставался доступным для этих клиентов. Однако бывает, что семантика просто расширяется. В этом случае предпочтительнее просто переадресовать запросы на активацию от старых клиентов на создание экземпляров нового класса.
Чтобы дать возможность разработчику новой версии класса прозрачно удовлетворять запросы на активацию для других CLSID , в СОМ введено понятие эмуляции классов (class emulation). Эмуляция классов позволяет разработчику компонента указать, что старый CLSID заменен новым, альтернативным CLSID, эмулирующим семантику исходного класса. Это позволяет прежним клиентам, делающим запросы на активацию с использованием прежнего CLSID, получать экземпляры нового усовершенствованного класса. Для индикации того, что у класса имеется новая альтернативная версия, в СОМ существует такая API-функция:
HRESULT CoTreatAsClass([in] REFCLSID rclsidOld, [in] REFCLSID rclsidNew);
Пусть Сhimp2 является новой версией класса Chimp, тогда следующий код проинформирует СОМ, что необходимо переадресовать запросы на активацию Chimp на запросы на активацию Chimp2:
// cause Chimp activation calls to activate Chimp2
// заставим запросы на активацию Chimp активизировать Chimp2
HRESULT hr = CoTreatAsClass(CLSID_Chimp, CLSID_Chimp2);
Эта API-функция добавляет следующий ключ реестра (registry key)
[HKCR\CLSID\{CLSID_Chimp}\TreatAs][1] @={CLSID_Chimp2}
Вызов CoTreatAsClass c CLSID_NULL в качестве второго параметра удаляет настройку TreatAs:
// cause Chimp activation calls to activate Chimps
// заставим запросы на активацию Chimp
// активизировать Chimps
HRESULT hr = CoTreatAsClass(CLSID_Chimp, CLSID_NULL);
Этот запрос восстанавливает исходную реализацию класса в состояние, предшествующее эмуляции. Клиенты могут запросить установку эмуляции данного класса, используя API-функцию CoGetTreatAsClass:
HRESULT CoGetTreatAsClass ([in] REFCLSID rclsidOld, [out] REFCLSID *pclsidNew);
Если запрошенный класс эмулируется другим классом, то CLSID эмулирующего класса будет возвращен посредством второго параметра и вся подпрограмма возвратит S_OK . Если же запрошенный класс не эмулируется другим классом, то посредством второго параметра будет возвращен исходный CLSID и подпрограмма возвратит S_FALSE. Необходимо также отметить, что в момент написания этой книги эмуляция классов не работает должным образом для удаленных запросов на активацию.
Категории компонентов
Как подчеркивалось в этой главе, основные примитивы активации СОМ требуют, чтобы вызывающей программе при создании новых экземпляров класса было известно его точное имя. Иногда, однако, бывает полезно просто потребовать, чтобы подходящим являлся любой класс, удовлетворяющий некоторым семантическим ограничениям. Кроме того, прежде чем сделать запрос на активацию, было бы полезно знать, какие сервисные средства класс требует от своих клиентов. В этом случае не будут создаваться объекты, которые клиент не готов должным образом поддерживать. Эти проблемы послужили причиной для создания категорий компонентов (component categories).
СОМ дает разработчикам возможность группировать родственные СОМ-классы в логические группы, или категории компонентов. Чаще всего все классы внутри категории будут реализовывать один и тот же набор интерфейсов. В то же время простое разделение пространства классов на части по признаку, какие интерфейсы какой класс реализует, не обеспечивает должного уровня модульности для многих приложений. Категории компонентов выступают как метаинформация, показывающая, какие классы совместимы с определенными семантическими ограничениями.
Категория компонентов есть группа логически родственных СОМ-классов, которые разделяют общий 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: