// reserved, must be zero
   // зарезервировано, должен быть нуль
   } COSERVERINFO;
 
   Если клиент не указывает имя хоста (host name), а использует только флаг CLSCTXREMOTESERVER, то для определения того, какая машина будет активировать объект, СОМ использует информацию о конфигурации каждого CLSID. Если клиент передает явное имя хоста, то оно получит приоритет перед любыми ранее сконфигурированными именами хостов, о которых может знать СОМ. Если клиент не желает передавать явную информацию о безопасности или имя хоста в CoGetClassObject, можно применить нулевой указатель COSERVERINFO. 
   Имея в наличии CoGetClassObject, клиент может дать запрос SCM на связывание указателя интерфейса с объектом класса:
 
   HRESULT GetGorillaClass(IApeClass * &rpgc)
   {
   // declare the CLSID for Gorilla as a GUID
   // определяем CLSID для Gorilla как GUID
   const CLSID CLSIDGorilla =
   { 0x571F1680, 0xCC83, 0x11d0,
   { 0x8C, 0х48, 0х00, 0х80, 0xС7, 0х39, 0x25, 0xBA
   } };
   // call CoGetClassObject directly
   // вызываем прямо CoGetClassObject
   return CoGetClassObject(CLSIDGorilla, CLSCTXALL, 0, IIDIApeClass, (void**)&rpgc);
   }
 
   Отметим, что если запрошенный класс доступен как внутрипроцессный сервер, то СОМ автоматически загрузит соответствующую DLL и вызовет известную экспортируемую функцию, которая возвращает указатель на требуемый объект класса[3]. Когда вызов CoGetClassObject завершен, библиотека СОМ и SCM полностью выходят из игры. Если бы класс был доступен только с внепроцессного или удаленного сервера, СОМ вместо этого возвратила бы заместитель, который позволил бы клиенту получить удаленный доступ к объекту класса.
   Напомним, что интерфейс IApeClass придуман для того, чтобы позволить клиентам находить или создавать экземпляры заданного класса. Рассмотрим следующий пример:
 
   HRESULT FindAGorillaAndEatBanana(long nGorillaID)
   {
   IApeClass *pgc = 0;
   // find the class object via CoGetClassObject
   // находим объект класса с помощью CoGetClassObject
   HRESULT hr = CoGetClassObject(CLSIDGorilla, CLSCTXALL, 0, IIDIApeClass, (void**)&pgc);
   if (SUCCEEDED(hr))
   {
   IApe *pApe = 0;
   // use the class object to find an existing gorilla
   // используем объект класса для нахождения существующей гориллы
   hr = pgc->GetApe(nGorillaID, &pApe);
   if (SUCCEEDED(hr))
   {
   // tell the designated gorilla to eat a banana
   // прикажем указанной горилле есть бананы
   hr = pApe->EatBanana();
   pApe->Release();
   }
   pgc->Release();
   }
   return hr;
   }
 
   Данный пример использует объект класса для того, чтобы Gorilla нашла именованный объект и проинструктировала его есть бананы. Чтобы этот пример работал, нужно, чтобы какой-нибудь внешний посредник дал вызывающему объекту имя какой-нибудь известной гориллы. Можно построить пример и таким образом, чтобы любая неизвестная горилла могла быть использована для удовлетворения запроса:
 
   HRESULT CreateAGorillaAndEatBanana(void)
   {
   IApeClass *pgc = 0;
   // find the class object via CoGetClassObject
   // находим объект класса с помощью CoGetClassObject
   HRESULT hr = CoGetClassObject(CLSIDGorilla, CLSCTXALL, 0, IIDIApeClass, (void**)&pgc);
   if (SUCCEEDED(hr))
   {
   IApe *pApe = 0;
   // use the class object to create a new gorilla
   // используем объект класса для создания новой гориллы
   hr = pgc->CreateApe(&pApe);
   if (SUCCEEDED(hr))
   {
   // tell the new gorilla to eat a banana
   // прикажем новой горилле есть бананы
   hr = pApe->EatBanana();
   pApe->Release();
   }
   pgc->Release();
   }
   return hr;
   }
 
   Отметим, что за исключением специфического использования метода IApeClass, эти примеры идентичны. Поскольку объекты класса могут экспортировать сколь угодно сложные интерфейсы, то их можно использовать для моделирования довольно изощренной стратегии активации, инициализации и размещения объектов.
 

Классы и серверы

   СОМ-сервер – это двоичный файл, содержащий код метода для одного или более СОМ-классов. Сервер может быть упакован или в динамически подключаемую библиотеку (DLL), или в нормальный исполняемый файл. В любом случае за загрузку любого типа сервера автоматически отвечает диспетчер управления сервисами SCM.
   Если в запросе на активацию объекта указана внутрипроцессная активация, то вариант сервера на базе DLL должен быть доступен для загрузки в адресном пространстве клиента. Если же в запросе на активацию указаны внепроцессная или внехостовая активация, то для запуска серверного процесса на указанной хост-машине (она может совпадать с машиной клиента) будет использован исполняемый файл. СОМ поддерживает также выполнение DLL-серверов в суррогатных процессах (surrogate processes) с целью разрешить использование внепроцессной и внехостовой активации существующих внутрипроцессных серверов. Подробности того, как суррогатные процессы связаны с внепроцессной и внехостовой активацией, будут изложены в главе 6.
   Чтобы клиенты могли активировать объекты, не беспокоясь о том, как упакован сервер или где он инсталлирован, в СОМ предусмотрена конфигурационная база данных, отображающая CLSID на тот сервер, который реализует этот класс. При использовании версий Windows NT 5.0 или выше основным местом расположения этой конфигурационной базы данных является директория NT (NT Directory). Эта директория является рассредоточенной защищенной базой данных, в которой хранится служебная информация об учетных записях пользователей, хост-машинах и прочее. С тем же успехом в директории NT можно хранить информацию и о СОМ-классах. Эта информация записывается в области директории, называемой СОМ Class Store (хранилище СОМ-классов). СОМ использует Class Store для перевода CLSID в файлы реализации (в случае локальных запросов на активацию) или в удаленные хост-имена (в случае удаленных запросов на активацию). Если запрос на активацию для CLSID сделан на данной машине, то в первую очередь опрашивается локальный кэш. Если в локальном кэше нет доступной конфигурационной информации, то СОМ посылает запрос в Class Store о том, чтобы реализация стала доступной из локальной машины. Это может просто означать добавление некоторой информации в локальный кэш, чтобы переадресовать запрос на другую хост-машину, или же это может привести к загрузке реализации класса на локальную машину и к запуску программы инсталляции. В любом случае, если класс зарегистрирован в Class Store, он доступен для запроса на активацию со стороны клиента в рамках ограничений безопасности.
   Локальный кэш, упоминавшийся при обсуждении Class Store, официально называется системным реестром, или базой конфигурации системы (Registry). Реестр является иерархической базой данных, хранящейся в файлах на каждой машине, которую СОМ использует для преобразования CLSID в имена файлов (в случае локальной активации) или удаленные имена хостов (в случае удаленной активации). До Windows NT 5.0 реестр был единственным местом размещения конфигурационной информации СОМ. Быстрый поиск в реестре может быть осуществлен с помощью иерархических ключей (keys), имена которых представляют собой строки, разделенные обратными косыми чертами. Каждый ключ в реестре может иметь одно или несколько значений, которые могут иметь в своем составе строки, целые значения или двоичные данные. В реализации СОМ на Windows NT 4.0 большая часть ее конфигурационной информации записывается под именем
   HKEYLOCALMACHINE\Software\Classes
   в то время как большинство программ используют более удобный псевдоним
   HKEYCLASSESROOT
   Реализация СОМ на Windows NT 5.0 продолжает использовать HKEYCLASSESROOT для установок в рамках всей машины, но также разрешает каждому пользователю назначить свою конфигурацию CLSID для обеспечения большей безопасности и гибкости. Под Windows NT 5.0 СОМ вначале опрашивает
   HKEYCURRENTUSER\Software\Classes 
   прежде чем опрашивать HKEYCLASSESROOT. Для удобства записи часто используются аббревиатуры HKLM, HKCR и HKCU вместо HKEYLOCALMACHINE, HKEYCLASSESROOT и HKEYCURRENTUSER , соответственно[1]
   СОМ хранит информацию, относящуюся к CLSID всех машин, под ключом реестра HKCR\CLSID
   В версии Windows NT 5.0 или выше СОМ ищет информацию о классах каждого пользователя под ключом HKCU\Software\Classes\CLSID
   Под одним из этих ключей будет сохранен список локальных CLSID, для каждого CLSID – свой подключ. Например, класс Gorilla, использовавшийся ранее в этой главе, мог бы иметь по всей машине запись по подключу[2]:
 
   [HKCR\CLSID\{571F1680-CC83-11d0-8C48-0080C73925BA}]
   @="Gorilla"
 
   Для обеспечения локальной активации объектов Gorilla запись для CLSID Gorilla в реестре должна иметь подключ, показывающий, какой файл содержит исполняемый код для методов класса. Если сервер упакован как DLL, то требуется такая запись:
 
   [HKCR\CLSID\{571F1680-CC83-11d0-8C48-0080C73925BA}\InprocServer32]
   @="C:\ServerOfTheApes.dll"
 
   Чтобы показать, что код упакован в исполняемом файле, требуется такая запись:
 
   [HKCR\CLSID\{571F1680-CC83-11d0-8C48-0080C73925BA}\LocalServer32]
   @="С:\ServerOfTheApes.exe"
 
   Допустимо обеспечивать обе записи и позволить клиенту выбирать местоположение, исходя из требований времени задержки и устойчивости. Для поддержки функции ProgIDFromCLSID необходимо указать такой подключ:
 
   [HKCR\CLSID\{571F1680-CC83-11d0-8C48-0080C73925BA}\ProgID]
   @="Apes.Gorilla.1"
 
   Наоборот, для поддержки функции CLSIDFromProgID требуются следующие величины:
 
   [HKCR\Apes.Gorilla.1] @="Gorilla" [HKCR\Apes.Gorilla.1\CLSID]
   @="\{571F1680-CC83-11d0-8C48-0080C73925BA}
 
   Программные идентификаторы (ProgID) не являются обязательными, но они рекомендуются, чтобы в тех средах, которые не могут просто копировать необработанные CLSID, тоже можно было осуществлять вызовы на активацию.
   Все стандартно реализованные серверы СОМ поддерживают саморегистрацию. Для внутрипроцессного сервера это означает, что DLL должен экспортировать известные функции
 
   STDAPI DllRegisterServer(void);
   STDAPI DllUnregisterServer(void);
 
   Отметим, что STDAPI является просто макросом, индицирующим, что функция возвращает НRESULT и использует стандартное соглашение СОМ по вызову глобальных функций. Эти подпрограммы должны быть явно экспортированы с использованием или файла определения модуля, или переключателей компоновщика, или директив компилятора. Эти подпрограммы используются хранилищем классов Class Store для конфигурирования локального кэша после загрузки файла на машину клиента. Кроме Class Store эти известные подпрограммы используются различными средами (например, Microsoft Transaction Server, ActiveX Code Download, а также различными инсталляционными программами) для инсталляции или деинсталляции серверов на хост-машинах. В Win32 SDK включена утилита REGSVR32.EXE, которая может инсталлировать или деинсталлировать внутрипроцессный сервер СОМ с использованием этих известных экспортированных функций.
   Реализации внутрипроцессных серверов DllRegisterServer и DllUnregisterServer должны запросить реестр на добавление или удаление соответствующих ключей, преобразующих CLSID и ProgID сервера в файловые имена сервера. Хотя существуют различные способы реализации этих подпрограмм, наиболее гибким и эффективным из них является создание строковой таблицы, содержащей соответствующие ключи, названия величин, сами величины и простое перечисление всех записей в таблице, путем вызова RegSetValueEx для инсталляции и RegDeleteKey для деинсталляции. Чтобы осуществить регистрацию, основанную на этой технологии, сервер может просто задать массив строк размером Nx3 , где каждый ряд массива содержит строки для использования в качестве ключей, имена величин и величины:
 
   const char *gRegTable[][3] = {
   // format is { key, value name, value }
   {
   «CLSID\\{571F1680-CC83-11d0-8C48-0080C73925BA}», 0, «Gorilla»
   },
   {
   "CLSID\\{571F1680-CC83-11d0-8C48-0080C73925BA}
   \\InprocServer32",0, (const char*)-1
   // rogue value indicating file name
   // нестандартное значение, указывающее имя файла
   },
   {
   «CLSID\\{571F1680-CC83-11d0-8C48-0080C73925BA}\\ProgID», 0, «Ареs.Gorilla.1»
   },
   {
   «Apes.Gorillа.1», 0, «Gorilla»
   },
   {
   «Apes.Gorilla.1\\CLSID», 0, «{571F1680-CC83-11d0-8C48-0080C73925BA}»
   },
   };
 
   Имея эту таблицу, весьма несложно осуществить реализацию DllRegisterServer:
 
   STDAPI DllRegisterServer(void)
   {
   HRESULT hr = SOK;
   // look up server's file name
   // ищем имя файла сервера
   char szFileName[MAXPATH];
   GetModuleFileNameA(ghinstDll, szFileName, MAXPATH);
   // register entries from table
   // регистрируем записи из таблицы
   int nEntries = sizeof(gRegTable)/sizeof(*gRegTable);
   for (int i = 0; SUCCEEDED(hr) && i < nEntries; i++)
   {
   const char *pszKeyName = gRegTable[i][0];
   const char *pszValueName = gRegTable[i][1];
   const char *pszvalue = gRegTable[i][2];
   // map rogue value to module file name
   // переводим нестандарное значение в имя файла модуля
   if (pszValue == (const char*)-1) pszValue = szFileName;
   HKEY hkey;
   // create the key
   // создаем ключ
   long err = RegCreateKeyA(HKEYCLASSESROOT, pszKeyName, &hkey);
   if (err == ERRORSUCCESS)
   {
   // set the value
   // присваиваем значение
   err = RegSetValueExA(hkey, pszVvalueName, 0, REGSZ, (const BYTE*) pszValue, (strlen(pszValue) + 1));
   RegCloseKey(hkey);
   }
   if (err != ERRORSUCCESS)
   {
   // if cannot add key or value, back out and fail
   // если невозможно добавить ключ или значение, то откат и сбой
   DllUnregisterServer();
   hr = SELFREGECLASS;
   }
   }
   return hr;
   }
 
   Соответствующая DllUnregisterServer будет выглядеть так:
 
   STDAPI DllUnregisterServer(void)
   {
   HRESULT hr = SOK;
   int nEntries = sizeof(gRegTable)/sizeof(*gRegTable);
   for (int i = nEntries – 1; i >= 0; i-)
   {
   const char *pszKeyName = gRegTable[i][0];
   long err = RegDeleteKeyA(HKEYCLASSESROOT, pszKeyName);
   if (err != ERRORSUCCESS) hr = SFALSE; }
   return hr;
   }
 
   Отметим, что реализация DllUnregisterServer просматривает таблицу с конца, начиная с последней входной точки. Делается это для преодоления ограничения RegDeleteKey, в котором разрешаются только такие ключи, у которых нет подключей, подлежащих удалению. Реализация DllUnregisterServer требует такой организации таблицы, чтобы все подключи каждого ключа появлялись в таблице после входа родительского ключа.
   Так как СОМ преобразует CLSID в данный файл реализации, то для объявления в СОМ относящихся к серверу объектов класса необходимо использовать определенные стандартные методики. Для сервера, основанного на исполняемой программе, в СОМ предусмотрены явные API-функции для связывания объектов класса с их CLSID . Эти API-функции мы будем подробно обсуждать в главе 6. Для сервера, основанного на DLL, DLL должна экспортировать известную функцию, которая будет вызываться с помощью CoGetClassObject, когда потребуется объект класса. Эту функцию необходимо экспортировать с использованием файла определения модулей, причем она должна иметь следующий вид:
 
   HRESULT DllGetClassObject(
   [in] REFCLSID rclsid,
   // which class object?
   // какой объект класса?
   [in] REFIID riid,
   // which interface?
   // какой интерфейс?
   [out, iidis(riid)] void **ppv);
   // put it here!
   // разместить его здесь!
 
   Для удобства и эффективности данный сервер может содержать код для более чем одного класса. Первый параметр DllGetClassObject показывает, какой класс в данный момент запрашивается. Второй и третий параметры просто дают функции возможность возвращать типизированный указатель интерфейса для СОМ.
   Рассмотрим сервер, реализующий три класса: Gorilla, Chimp и Orangutan. Сервер, возможно, будет содержать шесть отдельных классов C++: три из них создают экземпляры каждого класса, а другие три – объекты класса для каждого класса. В соответствии с этим сценарием, серверная реализация DllGetClassObject будет выглядеть следующим образом:
 
   STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
   {
   // define a singleton class object for each class
   // определяем одноэлементный объект класса
   // для каждого класса
   static GorillaClass sgorillaClass;
   static OrangutanClass sorangutanClass;
   static ChimpClass schimpClass;
   // return interface pointers to known classes
   // возвращаем указатели интерфейсов известных классов
   if (rclsid == CLSIDGorilla) return sgorillaClass.QueryInterface(riid, ppv);
   else if (rclsid == CLSIDOrangutan)
   return sorangutanClass.QueryInterface(riid, ppv);
   else if (rclsid == CLSIDChimp) return schimpClass.QueryInterface(riid, ppv);
   // if we get to here, rclsid is a class we don't implement,
   // so fail with well-known error code
   // если мы добрались сюда, то rclsid – это класс, который
   // мы не реализуем, поэтому сбой с известным кодом ошибки
   *ppv = 0;
   return CLASSECLASSNOTAVAILABLE;
   }
 
   Заметим, что приведенный код не заботится о том, какой интерфейс объявляется каждым из объектов класса. Он просто отправляет запрос QueryInterface соответствующему объекту класса.
   Следующий псевдокод показывает, как API-функция CoGetClassObject устанавливает связь с серверным DllGetClassObject:
 
   // pseudo-code from OLE32.DLL
   // псевдокод из OLE32.DLL
   HRESULT CoGetClassObject(REFCLSID rclsid, DWORD dwClsCtx, COSERVERINFO *pcsi , REFIID riid, void **ppv)
   {
   HRESULT hr = REGDBECLASSNOTREG;
   *ppv = 0; if (dwClsCtx & CLSCTXINPROC)
   {
   // try to perform inproc activation
   // пытаемся выполнить внутрипроцессную активацию
   HRESULT (*pfnGCO)(REFCLSID, REFIID, void**) = 0;
   // look in table of already loaded servers in this process
   // просматриваем таблицу уже загруженных серверов внутри
   // этого процесса pfnGCO = LookupInClassTable(rclsid, dwClsCtx);
   if (pfnGCO == 0) {
   // not loaded yet!
   // еще не загружен!
   // ask class store or registry for DLL name
   // запрашиваем DLL-имя в хранилище классов или в реестре
   char szFileName[MAXPATH];
   hr = GetFileFromClassStoreOrRegistry(rclsid, dwClsCtx, szFileName);
   if (SUCCEEDED(hr))
   {
   // try to load the DLL and scrape out DllGetClassObject
   // пытаемся загрузить DLL и вытащить DllGetClassObject
   HINSTANCE hInst = LoadLibrary(szFileName);
   if (hInst == 0) return COEDLLNOTFOUND;
   pfnGCO = GetProcAddress(hInst, «DllGetClassObject»);
   if (pfnGCO == 0) return COEERRORINDLL;
   // cache DLL for later use
   // кэшируем DLL для дальнейшего использования InsertInClassTable(rclsid, dwClsCtx, hInst, pfnGCO);
   }
   }
   // call function to get pointer to class object
   // вызываем функцию для получения указателя на объект класса
   hr = (*pfnGCO)(rclsid, riid, ppv);
   }
   if ((dwClsCtx & (CLSCTXLOCALSERVER | CLSCTXREMOTESERVER)) && hr == REGDBECLASSNOTREG)
   {
   // handle out-of-proc/remote request
   // обрабатываем внепроцессный/удаленный запрос
   }
   return hr;
   }
 
   Отметим, что реализация CoGetClassObject является единственным местом, откуда вызывается DllGetClassObject . Чтобы укрепить это обстоятельство, компоновщики в модели СОМ выдадут предупреждение в случае, если входная точка DllGetClassObject экспортируется без использования ключевого слова private в соответствующем файле определения модулей:
 
   // from APELIB.DEF
   // из APELIB.DEF LIBRARY
   APELIB EXPORTS DllGetClassObject private
 
   Фактически в модели СОМ компоновщики предпочитают, чтобы во всех связанных с СОМ точках входа использовалось это ключевое слово.
 

Обобщения

   В предыдущем примере интерфейс IApeClass рассматривался как интерфейс уровня класса, специфический для классов, которые объявляют интерфейс IАре из своих экземпляров. Этот интерфейс позволяет клиентам создавать новые объекты или находить существующие, но в любом случае результирующие объекты должны реализовывать интерфейс IАре . Если бы новый класс хотел разрешить клиентам создавать или находить объекты, несовместимые с IApe , то объект этого класса должен был бы реализовывать другой интерфейс. Поскольку создание и поиск объектов являются общими требованиями, которые большинство классов хотели бы поддерживать, СОМ определяет стандартные интерфейсы для моделирования поиска и создания объектов унифицированным образом (generically). Один стандартный интерфейс для поиска объектов назван IOleItemContainer:
 
   // from oleidl.idl из oleidl.idl
   [ object, uuid(0000011c-0000-0000-C000-000000000046) ]
   interface IOleItemContainer : IOleContainer {
   // ask for object named by pszItem
   // запрашиваем объект, именованный
   pszItem HRESULT Get0bject(
   [in] LPOLESTR pszItem,
   // which object? какой объект?
   [in] DWORD dwSpeedNeeded,
   // deadline
   [in, unique] IBindCtx *pbc,
   // binding info информация о связывании
   [in] REFIID riid,
   // which interface? какой интерфейс?
   [out, iidis(riid)] void **ppv);
   // put it here! разместим его здесь!
   // remaining methods deleted for clarity
   // остальные методы удалены для ясности
   }
 
   Отметим, что метод GetObject позволяет клиенту задавать тип результирующего интерфейсного указателя. Действительный класс результирующего объекта зависит от контекста и конкретной реализации IOleItemContainer . Следующий пример запрашивает объект класса Gorilla найти объект под именем «Ursus»:
 
   HRESULT FindUrsus(IApe * &rpApe)
   {
   // bind a reference to the class object
   // связываем ссылку с объектом класса
   rpApe = 0;
   IOleItemContainer *poic = 0;
   HRESULT hr = CoGetClassObject(CLSIDGorilla, CLSCTXALL, 0, IIDIOleItemContainer, (void**)&poic);
   if (SUCCEEDED(hr))
   {
   // ask Gorilla class object for Ursus
   // запрашиваем объект класса Gorilla на поиск
   Ursus hr = poic->GetObject(OLESTR(«Ursus»), BINDSPEEDINDEFINITE, 0, IIDIApe, (void**)&rpApe);
   poic->Release();
   }
   return hr;
   }
 
   Хотя такое использование вполне допустимо, интерфейс IOleItemContainer был предназначен для работы в тандеме с моникером элемента (Item Moniker), который будет рассматриваться позже в данной главе.
   В СОМ определен еще один стандартный интерфейс для создания объектов. Он называется IClassFactory:
 
   // from unknwn.idl из unknwn.idl
   [ object, uuid(00000001-0000-0000-C000-000000000046) ]
   interface IClassFactory : IUnknown
   {
   HRESULT CreateInstance( [in] IUnknown *pUnkOuter, [in] REFIID riid, [out, iidis(riid)] void **ppv) ;
   HRESULT LockServer([in] BOOL bLock);
   }
 
   Хотя экземпляры класса могли бы экспортировать интерфейс IClassFactory, данный интерфейс обычно экспортируется только объектами класса. Объекты класса не обязаны реализовывать IClassFactory, но, для единообразия, они часто делают это. В момент написания этой книги классы, которые будут встраиваться в среду Microsoft Transaction Server (MTS), должны реализовывать IClassFactory (фактически никакие другие интерфейсы объектов класса не будут распознаваться в MTS).
   Интерфейс IClassFactory имеет два метода: LockServer и CreateInstance. Метод LockServer вызывается внутри СОМ во время запроса на внепроцессную активацию и подробно обсуждается в главе 6. Метод CreateInstance используется для запроса на создание объектом класса нового экземпляра класса. Как было в случае IApeClass::CreateApe, тип объекта, который будет подвергаться обработке, определяется объектом класса, которому клиент посылает запрос CreateInstance. Первый параметр CreateInstance используется в агрегировании СОМ и обсуждается в главе 4. Пока же, в рамках третьей главы, для простоты изложения положим этот параметр равным нулю. Второй и третий параметры CreateInstance позволяют методу возвращать клиенту динамически типизируемый указатель интерфейса.
   Предполагая, что объект класса экспортирует интерфейс IClassFactory вместо IApeClass, клиенты должны использовать метод IClassFactory::CreateInstance для создания новых экземпляров:
 
   HRESULT CreateAGorillaAndEatBanana()
   {
   IClassFactory *pcf = 0;
   // find the class object находим объект класса
   HRESULT hr = CoGetClassObject(CLSIDGorilla, CLSCTXALL, 0, IIDIClassFactory, (void **)&pcf);
   if (SUCCEEDED(hr))
   {
   IApe *pApe = 0;
   // use the class object to create a gorilla
   // используем объект класса для создания gorilla
   hr = pcf->CreateInstance(0, IIDIApe, (void**)&pApe);
   // we're done with the class object, so release it
   // мы закончили с объектом класса, поэтому освобождаем его
   pcf->Release();
   if (SUCCEEDED(hr))
   {
   // tell the new gorilla to eat a banana
   // приказываем новой горилле есть банан
   hr = pApe->EatBanana();
   pApe->Release();
   }
   }
   return hr;
   }
 
   Этот код является семантически идентичным варианту с функцией, которая использовала интерфейс IApeClass вместо интерфейса IClassFactory.
   Для того чтобы предыдущий пример работал корректно, объекту класса Gorilla следует реализовать
 
   IClassFactory : class GorillaClass : public IClassFactory
   {
   public:
   IMPLEMENTUNKNOWNNODELETE(GorillaClass)
   BEGININTERFACETABLE(GorillaClass)
   IMPLEMENTSINTERFACE(IClassFactory)
   ENDINTERFACETABLE()
   STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv)
   {
   *ppv = 0;
   if (pUnkOuter != 0)
   // we don't support aggregation yet
   // мы еще не поддерживаем агрегирование
   return CLASSENOAGGREGATION;
   // create a new instance of our C++ class Gorilla
   // создаем новый экземпляр нашего С++-класса Gorilla
   Gorilla *p = new Gorilla;
   if (p == 0) return EOUTOFMEMORY:
   // increment reference count by one
   // увеличиваем счетчик ссылок на единицу
   p->AddRef();
   // store the resultant interface pointer into *ppv
   // записываем результирующий указатель интерфейса в *ppv
   HRESULT hr = p->QueryInterface(riid, ppv);
   // decrement reference count by one, which will delete the
   // object if QI fails
   // уменьшаем на единицу счетчик ссылок,
   // что уничтожит объект при отказе QI
   p->Release();
   // return result of Gorilla::QueryInterface
   // возвращаем результат работы Gorilla::QueryInterface
   return hr;
   } 
   STDMETHODIMP LockServer(BOOL bLock);
   };
 
   Реализация LockServer будет обсуждаться в этой главе позже. Отметим, что реализация CreateInstance, в первую очередь, создает новый объект C++ на базе класса Gorilla и запрашивает объект, поддерживает ли он нужный интерфейс. Если объект поддерживает требуемый интерфейс, то вызов QueryInterface инициирует вызов AddRef, и клиент в конечном счете выполнит соответствующий вызов Release. Если же произойдет отказ QueryInterface, то потребуется некоторый механизм для уничтожения созданного нового объекта. Предыдущий пример использует стандартную технологию «заключения в скобки» (bracketing) вызова QueryInterface между парой AddRef/Release. Если произошел сбой вызова QueryInterface, то вызов Release сбросит счетчик ссылок на нуль, запуская тем самым удаление объекта. Если же вызов QueryInterface прошел успешно, то вызов Release установит счетчик ссылок на единицу. Остающаяся ссылка принадлежит клиенту, который и выполнит последний вызов Release, когда объект более не нужен.
 

Оптимизации

   Одно из преимуществ использования стандартного интерфейса для создания экземпляров (instantiation ) состоит в том, что СОМ может обеспечить более эффективную технологию реализации. Рассмотрим следующий код, который создает новый экземпляр класса Chimp: