HRESULT CreateChimp(/* [out] */ IApe * &rpApe)
   {
   extern const CLSID CLSID_Chimp;
   rpApe = 0;
   IClassFactory *pcf = 0;
   HRESULT hr = CoGetClassObject(CLSID_Chimp, CLSCTX_ALL, 0, IID_IClassFactory, (void**)&pcf);
   if (SUCCEEDED(hr))
   {
   hr = pcf->CreateInstance(0, IID_IApe, (void**)&rpApe);
   pcf->Release();
   }
   return hr;
   }
 
   Этот код выполняет одну операцию: создание объекта Chimp. Заметим, что для выполнения одной операции понадобилось три вызова подопераций (suboperations) – CoGetClassObject, CreateInstance, Release. Если сервер загружен как внутрипроцессный, то эти три подоперации не обойдутся слишком дорого. Если, однако, сервер является внепроцессным или внехостовым, то каждая из этих подопераций потребует вызова между процессами клиента и сервера. Хотя СОМ использует очень эффективную передачу IPC/RPC (Interprocess Communication/Remote Procedure Call), каждая из этих подопераций потребует немалых исполнительных затрат. В идеале было бы лучше потребовать, чтобы СОМ перешел на серверный процесс один раз и, находясь там, использовал объект класса для вызова CreateInstance от имени клиента. Если объект класса используется только для реализации IClassFactory, то этот способ будет более эффективным, чем трехшаговый способ, показанный ранее. В СОМ имеется API-функция: CoCreateInstanceEx, относящаяся по своему назначению к той же категории, что CoGetClassObject и IClassFactory::CreateInstance – обеспечивающая создание новых объектов за одно обращение между процессами.
   CoCreateInstanceEx дает клиенту возможность задать CLSID для определения, какой объект следует реализовать. В случае успешного выполнения СоСгеаteInstanceEx возвращает один или более указателей интерфейса, которые ссылаются на новый экземпляр заданного класса. При использовании CoCreateInstanceEx клиент никогда не видит промежуточный объект класса, который используется для создания экземпляра объекта. Чтобы клиенты могли вызывать CoCreateInstanceEx, никаких дополнительных функций серверу реализовывать не нужно. С точки зрения сервера все, что необходимо, – это объявить свои объекты классов, как требуется для CoGetClassObject. Реализация CoCreateInstanceEx для нахождения соответствующего объекта класса будет использовать ту же технологию, что и CoGetClassObject. Основное различие заключается в том, что после нахождения объекта CoCreateInstanceEx выполняет дополнительный вызов IClassFactory::CreateInstance, за которым могут последовать один или более вызовов QueryInterface, и все это во время выполнения процесса объекта класса. Можно получить значительную экономию на этапе выполнения, если запрос на активацию закрепить за определенным процессом.
   Подобно CoGetClassObject, CoCreateInstanceEx позволяет клиенту задавать желаемые параметры CLSCTX и COSERVERINFO. Кроме того, CoCreateInstanceEx дает клиенту возможность запрашивать более одного указателя интерфейса на вновь создаваемый объект. Это делается путем предоставления клиенту возможности передавать массив структур MULTI_QI, который будет использован для вызова QueryInterface, новому экземпляру во время выполнения серверного процесса:
 
   typedef struct tagMULTI_QI {
   // which interface is desired?
   // какой интерфейс требуется?
   const IID *piid;
   // null on input, will contain the pointer on output
   // на входе нуль, на выходе будет содержать указатель
   [iid_is(piid)] IUnknown *pItf;
   // will contain the HRESULT from QueryInterface on output
   // на выходе будет содержать HRESULT от QueryInterface
   HRESULT hr;
   }
   MULTI_QI;
 
   Так как клиент может запрашивать более чем один указатель интерфейса на новый объект, он более не нуждается в явном вызове QueryInterface, если ему требуется более чем один тип указателей интерфейса. Поскольку эти вызовы QueryInterface будут сделаны СОМ от имени клиента по время выполнения внутри процесса объекта класса, более не потребуется никаких обменов данными между клиентом и объектом. Отметим, что каждый из указателей интерфейса, возвращенных CoCreateInstanceEx , будет указывать на один и тот же объект. СОМ не поддерживает внутреннего режима создания нескольких экземпляров за одно обращение.
   После постижения структуры MULTI_QI понять определение CoCreateInstanceEx уже нетрудно:
 
   HRESULT CoCreateInstanceEx( [in] REFCLSID rclsid,
   // what kind of object? – какого сорта объект?
   [in] IUnknown *pUnkOuter,
   // for aggregation – для агрегирования
   [in] DWORD dwClsCtx,
   // locality? – размещение?
   [in] COSERVERINFO *pcsi,
   // host/security info – информация о хосте/безопасности
   [in] ULONG cmqi,
   // how many interfaces? – сколько интерфейсов?
   [out, size_is (cmqi)] MULTI_QI *prgmq
   // where to put itfs – куда разместить интерфейсы );
 
   Если все запрошенные интерфейсы доступны в новом объекте, то CoCreateInstanceEx возвращает S_OK. Если хотя бы один (но не все) из запрошенных интерфейсов недоступен, то CoCreateInstanceEx возвратит CO_S_NOTALLINTERFACES, индицируя частичный успех. Затем вызывающий объект должен исследовать индивидуальные HRESULT в каждой структуре MULTI_QI, чтобы определить, какие интерфейсы были доступны, а какие – нет. Если CoCreateInstanceEx не может создать объект или ни один из запрошенных интерфейсов не доступен, то CoCreateInstanceEx возвращает HRESULT с кодом серьезности ошибки SEVERITY_ERROR, который сообщит, почему произошел отказ в операции.
   CoCreateInstanceEx чрезвычайно эффективен в случае, когда требуются интерфейсы нескольких типов. Рассмотрим дополнительное определение интерфейса:
 
   [object,uuid(753A8F7C-A7FF-11d0-8C30-0080C73925BA)]
   interface IEgghead : IUnknown
   {
   import «unknwn.idl»;
   HRESULT ContemplateNavel(void);
   }
 
   Имея такое определение интерфейса, клиент может привязать оба типа указателей к новому шимпанзе за одно обращение:
 
   void CreateChimpEatBananaAndThinkAboutIt(void)
   {
   // declare and initialize an array of MULTI_QI's
   // объявляем и инициализируем массив MULTI_QI
   MULTI_QI rgmqi[2] = { { &IID_IApe, 0, 0 }, { &IID_IEgghead, 0, 0 } };
   HRESULT hr = CoCreateInstanceEx( CLSID_Chimp,
   // make a new chimp – создаем нового шимпанзе
   0,
   // no aggregation – без агрегирования
   CLSCTX_ALL,
   // any locality – размещение любое
   0,
   // no explicit host/security info
   // нет явной информации о хосте и безопасности
   2,
   // asking for two interfaces – запрашиваем 2 интерфейса
   rgmqi);
   // array of MULTI_QI structs – массив структур
   MULTI_QI
   if (SUCCEEDED(hr)) {
   // hr may be CO_S_NOTALLINTERFACES, so check each result
   // hresult может быть равен CO_S_NOTALLINTERFACES,
   // поэтому проверяем каждый результат
   if (hr == S_OK || SUCCEEDED(rgmqi[0].hr)) {
   // it is safe to blindly cast the resultant ptr to the type
   // that corresponds to the IID used to request the interface
   // безопасно вслепую преобразовать результирующий
   // указатель к типу, соответствующему тому IID,
   // который использовался при запросе интерфейса
   IАре *рАре = reinterpret_cast<IApe*>(rgmqi[0].pItf);
   assert(pApe);
   HRESULT hr2 = pApe->EatBanana();
   assert(SUCCEEDED(hr2));
   pApe->Release();
   }
   if(hr == S_OK || SUCCEEDED(rgmqi[1].hr)) {
   IEgghead *peh = reinterpret_cast<IEgghead*>(rgmqi[1].pItf);
   assert(peh);
   HRESULT hr2 = peh->ContemplateNavel();
   assert(SUCCEEDED(hr2));
   peh->Release();
   }
   } }
 
 
   Рисунок 3.3 показывает шаги, которые предпринимаются CoCreateInstanceEx для создания нового объекта. Важно отметить, что оба результирующих указателя будут указывать на один и тот же объект. Если нужны два различных объекта, то требуются и два отдельных вызова CoCreateInstanceEx.
 
   Использование СоСrеateInstanceЕх достаточно просто, если нужен только один интерфейс:
 
   HRESULT CreateChimpAndEatBanana(void)
   {
   // declare and Initialize a MULTI_QI
   // определяем и инициализируем MULTI_QI
   MULTI_QI mqi = { &IID_IApe, 0, 0 };
   HRESULT hr = CoCreateInstanceEx( CLSID_Chimp,
   // make a new chimp – создаем нового шимпанзе
   0,
   // по aggregation – без агрегирования CLSCTX_ALL,
   // any locality – любое расположение
   0,
   // no explicit host/security Info
   // нет явной информации о хосте/безопасности
   1,
   // asking for one interface – запрашиваем один интерфейс
   &mqi);
   // array of MULTI_QI structs – массив структур
   MULTI_QI
   if (SUCCEEDED(hr))
   {
   IApe *pApe = reinterpret_cast<IApe*>(mqi.pItf);
   assert(pApe);
   // use the new object – используем новый объект
   hr = pApe->EatBanana();
   // release the Interface pointer
   // освобождаем указатель интерфейса
   pApe->Release();
   }
   return hr;
   }
 
   Если нужен только один интерфейс и не передается COSERVERINFO, то СОМ предусматривает несколько более удобную версию CoCreateInstanceEx, именуемую CoCreateInstance[1]:
 
   HRESULT CoCreateInstance( [in] REFCLSID rclsid,
   // what kind of object? – какой тип объекта?
   [in] IUnknown *pUnkOuter,
   // for aggregation – для агрегирования
   [in] DWORD dwClsCtx,
   // locality? – размещение?
   [in] REFIID riid,
   // what kind of interface – какой вид интерфейса
   [out, iid_is(riid)] void **ppv);
   // where to put itf – куда разместить интерфейс
 
   Предыдущий пример становится намного проще, если применить CoCreateInstance
 
   HRESULT CreateChimpAndEatBanana(void)
   {
   IАре *рАре = 0;
   HRESULT hr = CoCreateInstance( CLSID_Chimp,
   // make a new chimp – создаем нового шимпанзе
   0,
   // по aggregation – без агрегирования
   CLSCTX_ALL,
   // any locality – любое расположение
   IID_IApe,
   // what kind of itf – какой вид интерфейса
   (void**)&pApe);
   // where to put iff – куда поместить интерфейс
   if (SUCCEEDED(hr)) {
   assert(pApe);
   // use the new object используем новый объект
   hr = pApe->EatBanana();
   // release the interface pointer
   // освобождаем указатель интерфейса
   pApe->Release();
   }
   return hr;
   }
 
   Оба предыдущих примера функционально эквивалентны. В сущности, реализация CoCreateInstance просто осуществляет внутренний вызов CoCreateInstanceEx:
 
   // pseudo-code for implementation of CoCreateInstance API
   // псевдокод для реализации API-функции
   CoCreateInstance HRESULT
   CoCreateInstance(REFCLSID rclsid, IUnknown *pUnkOuter, DWORD dwCtsCtx, REFIID riid, void **ppv)
   {
   MULTI_QI rgmqi[] = { &riid, 0, 0 };
   HRESULT hr = CoCreateInstanceEx(rclsid, pUnkOuter, dwClsCtx, 0, 1, rgmqi);
   *ppv = rgmqi[0].pItf;
   return hr;
   }
 
   Хотя возможно выполнить запрос на удаленную активацию с использованием CoCreateInstance, отсутствие параметра COSERVERINFO не позволяет вызывающему объекту задать явное имя хоста. Вместо этого вызов CoCreateInstance и задание только флага CLSCTX_REMOTE_SERVER предписывает SCM использовать конфигурационную информацию каждого CLSID для выбора хост-машины, которая будет использоваться для активации объекта.
 
 
   Рисунок 3.4 показывает, как параметры CoCreateInstanceEx преобразуются в параметры CoGetClassObject и IClassFactory::CreateInstance. Вопреки распространенному заблуждению, CoCreateInstanceEx не осуществляет внутренний вызов CoGetClassObject. Хотя между двумя этими методиками нет логических различий, реализация CoCreateInstanceEx будет более эффективной при создании одного экземпляра, так как в этом случае не будет лишних вызовов клиент-сервер, которые могли бы понадобиться, если бы была использована CoGetClassObject. Если, однако, будет создаваться большое число экземпляров, то клиент может кэшировать указатель объекта класса и просто вызвать IClassFactory::CreateInstance несколько раз. Поскольку IClassFactory::CreateInstance является всего лишь вызовом метода и не идет через SCM, он отчасти быстрее, чем вызов CoCreateInstanceEx. Порог, за которым становится более эффективным кэшировать объект класса и обходить CoCreateInstanceEx, будет изменяться в зависимости от эффективности IPC и RPC на используемых хост-машинах и сети.
 

Снова интерфейс и реализация

   В предыдущих примерах активации со стороны клиента осуществлялись явные вызовы API-функций СОМ для активации. Часто может понадобиться много шагов для корректной связи с требуемым объектом (например, создать один тип объекта, затем запросить его для ссылки на другой объект, основанный на некоторой информации в запросе). Чтобы избавить клиентов от заботы об алгоритмах по поиску объектов или их созданию, СОМ поддерживает стандартный механизм назначения произвольных имен объектам, на которые они ссылаются. Этот механизм основан на использовании локаторных объектов (locator objects), которые инкапсулируют свой алгоритм связи, скрывая его за стандартным постоянным интерфейсом. Эти локаторы объектов формально называются моникерами и являются просто СОМ-объектами, экспортирующими интерфейс IMoniker. Интерфейс IMoniker является одним из наиболее сложных интерфейсов СОМ; тем не менее, он объявляет один метод, чрезвычайно важный для данной дискуссии, а именно BindToObject:
 
   interface IMoniker : IPersistStream { HRESULT BindToObject([in] IBindCtx *pbc, [in, unique] IMoniker *pmkToLeft, [in] REFIID riid, [out, iid_is(riid)] void **ppv);
   // remaining methods deleted for clarity
   // остальные методы удалены для ясности
   }
 
   Напоминаем, что определения интерфейса являются абстрактными и достаточно неопределенными для того, чтобы допустить множество интерпретаций точной семантики каждого метода. Абстрактную семантику BindToObject можно сформулировать так: «запусти свой алгоритм поиска или создания объекта и возврати типизированный интерфейсный указатель на этот объект, когда он создан или найден». Когда клиент вызывает ВindToObject на моникер, у него нет точных представлений о том, как именно моникер превратит свою внутреннюю структуру в указатель на объект. Имея три различных моникера, можно использовать три совершенно различных алгоритма. Такая полиморфность поведения и делает идиому моникера столь действенной.
   Клиенты получают моникеры одним из следующих способов. Клиент может получить моникер от какого-нибудь внешнего агента, такого, как результат вызова метода на некоем уже существующем объекте. Клиенты могут вызвать явную API-функцию, которая создает моникер определенного типа. Клиенты могут просто иметь строку текста, являющуюся «строкоподобным» состоянием моникера. Последний случай является наиболее интересным, так как он позволяет приложениям загружать и сохранять «имена объектов», используя внешние конфигурационные файлы или системный реестр, в текстовом виде (text-based). Если эта методика открыто документирована как часть конфигурационного состояния приложения, системные администраторы или опытные пользователи смогут переконфигурировать свое приложение, чтобы использовать альтернативную технологию для поиска объектов, которая могла быть или не быть предусмотрена разработчиком исходного приложения. Например, моникер, поддерживающий выравнивание нагрузки, может быть переконфигурирован для проведения иной стратегии выбора хост-машин простым изменением текстовой версии моникера, которая хранится в конфигурационном файле приложения.
   Текстовое представление моникера формально называется отображаемым именем (display name). Интерфейс IMoniker объявляет метод GetDisplayName, который позволяет клиентам запрашивать моникер о его отображаемом имени. Более интересная задача – превратить произвольные отображаемые имена в моникеры. Эта задача довольно проблематичная, так как клиент не может просто сказать, какому виду моникера соответствует отображаемое имя. Такую работу выполняет MkParseDisplayName – вероятно, наиболее важная API-функция во всем СОМ.
   MkParseDisplayName берет произвольное отображаемое имя и превращает его в моникер:
 
   HRESULT MkParseDisplayName(
   [in] IBindCtx *pbc,
   // binding Info – информация о связывании
   [in, string] const OLECHAR *pwszName,
   // object name – имя объекта
   [out] ULONG *pcchEaten,
   // progress on error – сообщение об ошибке
   [out] IMoniker **ppmk);
   // the resultant moniker – результирующий моникер
 
   Пространство имен моникеров является расширяемым, чтобы поддерживать новые типы моникеров. Синтаксический анализатор высокого уровня, использованный в MkParseDisplayName, исследует префикс отображаемого имени и пытается сопоставить его с зарегистрированным префиксом ProgID, который определяет, какому типу моникера соответствует данное отображаемое имя. Если префиксы совпадают, то создается новый моникер соответствующего типа и ему присваивается отображаемое имя для дальнейшего анализа. Поскольку моникеры имеют иерархическую структуру и могут быть композитными, то результирующий моникер в действительности может содержать два и более моникеров. Клиент может не заботиться об этой детали реализации. Клиент попросту использует для нахождения искомого объекта результирующий интерфейсный указатель IMoniker, который может указывать, а может не указывать на композитный моникер (composite moniker).
   Напомним, что начальная точка входа в класс СОМ проходит через объект этого класса. Чтобы связаться с объектом класса, необходим моникер классового типа (Class Moniker). Это моникеры встроенного типа, предоставляемые моделью СОМ. Классовые моникеры поддерживают CLSID в качестве своего начального состояния и могут быть созданы либо с помощью явной API-функции СОМ CreateClassMoniker.
 
   HRESULT CreateClassMoniker([in] REFCLSID rclsid, [out] IMoniker **ppmk);
 
   либо путем передачи отображаемого имени от Class Moniker в MkParseDisplayName[1]:
 
   clsid:571F1680-CC83-11d0-8C48-0080C73925BA:
 
   Отметим, что префикс «сlsid» является программным идентификатором ProgID для Class Moniker. Следующий код демонстрирует использование МkParseDisplayName для создания Class Moniker, который затем используется для связи с объектом класса Gorilla:
 
   HRESULT GetGorillaClass(IApeClass * &rpgc)
   { rpgc = 0;
   // declare the CLSID for Gorilla as a display name
   // объявляем CLSID как отображаемое имя для Gorilla
   const OLECHAR pwsz[] = OLESTR(«clsid:571F1680-CC83-11d0-8C48-0080C73925BA:»);
   // create a new binding context for parsing
   // and binding the moniker
   // создаем новый связующий контекст
   // для анализа и связывания моникера
   IBindCtx *pbc = 0;
   HRESULT hr = CreateBindCtx(0, &pbc);
   if (SUCCEEDED(hr))
   {
   ULONG cchEaten; IMoniker *pmk = 0;
   // ask СОМ to convert the display name to a moniker object
   // запрашиваем СОМ преобразовать отображаемое имя
   // в объект моникера
   hr = MkParseDisplayName(pbc, pwsz, &cchEaten, &pmk);
   if (SUCCEEDED(hr))
   {
   // ask the moniker to find or create the object that it
   // refers to // запрашиваем моникер найти или создать объект,
   // на который моникер ссылается
   hr = pmk->BindToObject(pbc, 0, IID_IApeClass, (void**)&rpgc);
   // we now have a pointer to the desired object, so release
   // the moniker and the binding context
   // теперь у нас есть указатель на желаемый объект, так что
   // освобождаем моникер и связующий контекст
   pmk->Release();
   }
   pbc->Release();
   }
   return hr;
   }
 
   Связующий контекст, который передается одновременно в MkParseDisplayName и IMoniker::BindToObject, является просто вспомогательным объектом, который позволяет дополнительным параметрам передаваться алгоритмам синтаксического анализа и связывания моникера. В случае нашего простого примера все, что требуется, – это новый связующий контекст в роли заполнителя (placeholder), который запрашивается путем вызова API-функции СОМ CreateBindCtx[2].
   В Windows NT 4.0 введена API-функция, упрощающая вызовы MkParseDisplayName и IMoniker::BindToObject:
 
   HRESULT CoGetObject( [in, string] const OLECHAR *pszName, [in, unique] BIND_OPTS *pBindOptions, [in] REFIID riid, [out, iid_is(riid)] void **ppv);
 
   Эта API-функция реализована следующим образом:
 
   // pseudo-code from OLE32.DLL
   // псевдокод из OLE32.DLL
   HRESULT CoGetObject(const OLECHAR *pszName, BIND_OPTS *p0pt, REFIID riid, void **ppv)
   {
   // prepare for failure
   // подготовка на случай сбоя
   *ppv = 0;
   // create a bind context
   // создаем контекст связывания
   IBindCtx *pbc = 0;
   HRESULT hr = CreateBindCtx(0, &pbc);
   if (SUCCEEDED(hr))
   {
   // set bind options if provided
   // устанавливаем опции связывания, если они требуются
   if (pOpt) hr = pbc->SetBindOptions(pOpt);
   if (SUCCEEDED(hr))
   {
   // convert the display name into a moniker
   // преобразуем отображаемое имя в моникер
   ULONG cch;
   IMoniker *pmk = 0;
   hr = MkParseDisplayName(pbc, pszName, &cch, &pmk);
   if (SUCCEEDED(hr)) {
   // ask the moniker to bind to the named object
   // запрашиваем моникер связаться с именованным объектом
   hr = pmk->BindToObject(pbc, 0, riid, ppv);
   pmk->Release();
   }
   }
   pbc->Release();
   }
   return hr;
   }
 
   При наличии этой функции создание новой гориллы сводится к простому нахождению объекта класса и вызову метода CreateInstance:
 
   HRESULT CreateAGorillaAndEatBanana() {
   IClassFactory *pcf = 0;
   // declare the CLSID for Gorilla as a display name
   // объявляем CLSID как отображаемое имя для Gorilla
   const OLECHAR pwsz[] = OLESTR(«clsid:571F1680-CC83-11d0-8C48-0080C73925BA:»);
   // find the class object via the gorilla's class moniker
   // находим объект класса через gorilla's class moniker
   HRESULT hr = CoGetObject(pwsz, 0, IID_IClassFactory, (void**)&pcf);
   if (SUCCEEDED(hr))
   {
   IApe *pApe = 0;
   // use the class object to create a gorilla
   // используем объект класса для создания gorilla
   hr = pcf->CreateInstance(0, IID_IApe, (void**)&pApe);
   if (SUCCEEDED(hr)) {
   // tell the new gorilla to eat a banana
   // говорим новой горилле съесть банан
   hr = pApe->EatBanana();
   pApe->Release();
   }
   pcf->Release();
   }
   return hr;
   }
 
 
   Рисунок 3.5 иллюстрирует, какие объекты создаются или находятся посредством каждой операции.
   Visual Basic предоставляет функциональные возможности API-функции CoGetObject через встроенную функцию GetObject. Следующий код на Visual Basic также создает новую gorilla и предписывает ей есть бананы:
 
   Sub CreateGorillaAndEatBanana()
   Dim gc as IApeClass
   Dim ape as IApe
   Dim sz as String sz = «clsid:571F1680-CC83-11d0-8C48-0080C73925BA:»
   ' get the class object for gorillas
   ' получаем объект класса для gorilla
   Set gc = GetObject(sz)
   ' ask Gorilla class object to create a new gorilla
   ' запрашиваем объект класса Gorilla создать новую gorilla
   Set ape = gc.CreateApe()
   ' ask gorilla to eat a banana
   ' просим gorilla есть бананы
   ape.EatBanana
   End Sub
 
   Отметим, что версия этой функции на Visual Basic использует интерфейс IApeClass для обработки объекта. Это связано с тем, что Visual Basic не может использовать интерфейс IClassFactory из-за ограничений языка.
 

Моникеры и композиция

   Моникеры часто составляются из других моникеров, чтобы с помощью текстового описания пути можно было осуществлять навигацию по иерархиям объектов. Чтобы обеспечить простую поддержку этого типа управления, в СОМ предусмотрена стандартная реализация моникеров, которая, будучи поставленной справа от другого моникера, запрашивает объект связать ссылку с другим объектом в иерархии. Такой моникер называется моникером элемента (Item Moniker) и использует интерфейс объекта IOleItemContainer для преобразования имени объекта в интерфейсный указатель.
   Следующее отображаемое имя показывает, как моникер элемента использован в тандеме с классовым моникером:
 
   clsid:571F1680-CC83-11d0-8C48-0080C73925BA:!Ursus
 
   Отметим использование символа "!" для отделения отображаемого имени Class Moniker от имени элемента (item name) «Ursus». При анализе MkParseDisplayName сначала использует префикс "clsid " в качестве ProgID для контакта с реализацией Сlass Moniker. Затем MkParseDisplayName предложит реализации Class Moniker проанализировать часть строки – столько, сколько она сможет распознать. Это означает, что после того, как Сlass Moniker извлек свой GUID из строки, ее следующий фрагмент все еще нуждается в анализе: !Ursus
   Поскольку это имя имеет смысл только в области действия объекта, именованного моникером слева от него, фактически MkParseDisplayName присваивает значение крайнему левому моникеру (моникеру классового типа) и запрашивает объект, который он именует (объект класса Gorilla) проанализировать оставшуюся часть строки. Для поддержки анализа отображаемых имен СОМ определяет стандартный интерфейс IParseDisplayName:
 
   [ object,uuid(0000011a-0000-0000-C000-000000000046) ]
   interface IParseDisplayName : IUnknown
   {
   // convert display name to a moniker
   // преобразуем отображаемое имя в моникер
   HRESULT ParseDisplayName( [in, unique] IBindCtx *pbc, [in] LPOLESTR pszDisplayName, [out] ULONG *pchEaten, [out] IMoniker **ppmkOut );
   }
 
   В случае отображаемого имени, использованного в этом примере, объекту класса Gorilla потребуется реализовать IParseDisplayName и преобразовать строку "!Ursus" в моникер, который MkParseDisplayName поставит справа от моникера классового типа. Поскольку требуется стандартный моникер элемента, то достаточно будет такой реализации:
 
   STDMETHODIMP GorillaClass::ParseDisplayName(IBindCtx *pbc, LPOLESTR pszDisplayName, ULONG *pchEaten, IMoniker **ppmkOut)
   {
   // create an item moniker using explicit API function
   // создаем отдельный моникер, используя явную API-функцию
   HRESULT hr = CreateItemMoniker(OLESTR("!"), pszDisplayName + 1, ppmkOut);
   // indicate how many characters were parsed
   // показываем, сколько символов было проанализировано
   if (SUCCEEDED(hr)) *pchEaten = wcslen(pszDisplayName);
   else *pchEaten = 0; return hr;
   }
 
   Отметим, что в данном примере не делается попытки проверить правильность анализируемого имени. Здесь просто убирается начальный символ "!", и из оставшейся части отображаемого имени создается новый моникер элемента.
   Так как было проанализировано два моникера, то MkParseDisplayName будет собирать эти моникеры вместе, используя групповой композитный моникер (generic composite moniker). Групповой композитный моникер просто удерживает два моникера вместе. Реализация группового композитного моникера BindToObject сначала связывает моникер справа, передавая ему указатель на моникер слева через параметр pmkToLeft. Это иллюстрируется следующим псевдокодом: