Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Новости ИТ Программы Книги Games   Обои   Экспорт RSS E-Books
 
Поиск по сайту

TOP-10 программ
Symantec Norton Ghost 9.0
Partition Magic 8.0.2 Pro
Xilisoft 3GP Video Converter v3.1.7.0616b
Norton AntiVirus 2005
Xilisoft 3GP Video Converter v2.1.52.831b
Антивирус Касперского Personal 5.0.303 beta 2
RAR Password Cracker 4.12
ABBYY PDF Transformer v1.00.820
MP3 To Ringtone Gold v3.02
Windows Movie Maker 2.6
 
Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
 
 

   Программирование -> C/C++ -> Сущность технологии COM


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

СОМ-сервер - это двоичный файл, содержащий код метода для одного или более СОМ-классов. Сервер может быть упакован или в динамически подключаемую библиотеку (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 большая часть ее конфигурационной информации записывается под именем

HKEY_LOCAL_MACHINE\Software\Classes 

в то время как большинство программ используют более удобный псевдоним

HKEY_CLASSES_ROOT

Реализация СОМ на Windows NT 5.0 продолжает использовать HKEY_CLASSES_ROOT для установок в рамках всей машины, но также разрешает каждому пользователю назначить свою конфигурацию CLSID для обеспечения большей безопасности и гибкости. Под Windows NT 5.0 СОМ вначале опрашивает

HKEY_CURRENT_USER\Software\Classes

прежде чем опрашивать HKEY_CLASSES_ROOT. Для удобства записи часто используются аббревиатуры HKLM, HKCR и HKCU вместо HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT и HKEY_CURRENT_USER, соответственно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 *g_RegTable[][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 = S_OK;
      // look up server's file name 
      // ищем имя файла сервера
    char szFileName[MAX_PATH];
    GetModuleFileNameA(g_hinstDll, szFileName, MAX_PATH);
      // register entries from table 
      // регистрируем записи из таблицы 
    int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable);
    for (int i = 0; SUCCEEDED(hr) && i < nEntries; i++) { 
        const char *pszKeyName   = g_RegTable[i][0];
        const char *pszValueName = g_RegTable[i][1];
        const char *pszvalue     = g_RegTable[i][2];
          // map rogue value to module file name 
          // переводим нестандарное значение в имя файла модуля 
        if (pszValue == (const char*)-1) 
            pszValue = szFileName;
        HKEY hkey;
          // create the key 
          // создаем ключ
        long err = RegCreateKeyA(HKEY_CLASSES_ROOT, pszKeyName, &hkey);
        if (err == ERROR_SUCCESS) { 
          // set the value 
          // присваиваем значение 
            err = RegSetValueExA(hkey, pszVvalueName, 0,
                                 REG_SZ, (const BYTE*) pszValue, 
                                 (strlen(pszValue) + 1));
            RegCloseKey(hkey);
        }
        if (err != ERROR_SUCCESS) {
              // if cannot add key or value, back out and fail
              // если невозможно добавить ключ или значение, то откат и сбой
            DllUnregisterServer();
            hr = SELFREG_E_CLASS;
        }
    }
    return hr;
}

Соответствующая DllUnregisterServer будет выглядеть так:

STDAPI DllUnregisterServer(void)
{
    HRESULT hr = S_OK;
    int nEntries = sizeof(g_RegTable)/sizeof(*g_RegTable);
    for (int i = nEntries - 1; i >= 0; i--) { 
        const char *pszKeyName = g_RegTable[i][0];
        long err = RegDeleteKeyA(HKEY_CLASSES_ROOT, pszKeyName);
        if (err != ERROR_SUCCESS) 
          hr = S_FALSE;
    }
    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, iid_is(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 s_gorillaClass;
    static OrangutanClass s_orangutanClass;
    static ChimpClass s_chimpClass;
      // return interface pointers to known classes
      // возвращаем указатели интерфейсов известных классов
    if (rclsid == CLSID_Gorilla)
        return s_gorillaClass.QueryInterface(riid, ppv);
    else if (rclsid == CLSID_Orangutan)
        return s_orangutanClass.QueryInterface(riid, ppv);
    else if (rclsid == CLSID_Chimp)
        return s_chimpClass.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 CLASS_E_CLASSNOTAVAILABLE;
}

Заметим, что приведенный код не заботится о том, какой интерфейс объявляется каждым из объектов класса. Он просто отправляет запрос 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 = REGDB_E_CLASSNOTREG;
    *ppv = 0;
    if (dwClsCtx & CLSCTX_INPROC) {
          // 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[MAX_PATH];
            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 CO_E_DLLNOTFOUND;
                pfnGCO = GetProcAddress(hInst, "DllGetClassObject");
                if (pfnGCO == 0) return CO_E_ERRORINDLL;
                  // 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 & (CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER))
        && hr == REGDB_E_CLASSNOTREG) { 
          // handle out-of-proc/remote request 
          // обрабатываем внепроцессный/удаленный запрос
    }
    return hr;
}

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

// from APELIB.DEF 
// из APELIB.DEF 
LIBRARY APELIB 
EXPORTS
    DllGetClassObject private

Фактически в модели СОМ компоновщики предпочитают, чтобы во всех связанных с СОМ точках входа использовалось это ключевое слово.


1 Эти аббревиатуры не допускаются в исходном коде или в конфигурационных файлах. Они просто дают возможность длинным именам ключей фигурировать в виде одной строки без разделителей в документации или других текстах о СОМ. Читателю следует раскрывать аббревиатуры при чтении вслух или при использовании в исходном коде.

2 Приведенный здесь способ записи использует стандартный синтаксис REGEDIT4. Строки, содержащиеся внутри скобок, соответствуют именам ключей. Пары имя=значение (name = value) под ключом обозначают значения, присвоенные указанному ключу. Необычное имя "@" показывает значение ключа по умолчанию.

Обобщения
Оптимизации
Снова интерфейс и реализация
Моникеры и композиция
Моникеры и сохраняемость
Время жизни сервера
Классы и IDL
Эмуляция классов
Категории компонентов
Где мы находимся?

 
Популярные книги

Общая информатика. Универсальный курс

Подробнее

SQL для "чайников", 5-е издание

Подробнее

Дизайн помещений и интерьеров в 3ds max 7 (+CD)

Подробнее


 
Новости ИТ
09.01.2009  Exeda -- корпоративный цифровой ассистент с Android Linux
09.01.2009  Правительство Вьетнама массово переходит на Open Source
09.01.2009  Windows 7 build 7000
09.01.2009  Silicon Power представила скоростную SDHC
09.01.2009  CES 2009: RealView 360 3D Desktop Scanner - настольный 3D-сканер, один из первых в мире
09.01.2009  W90 - очень быстрый мультимедийный ноутбук ASUS «Ultimate-уровня»
09.01.2009  CES 2009: SanDisk представила семейство G3 - самых быстрых SSD-накопителей на флэш-памяти MLC
09.01.2009  ZOTAC GeForce GTX 285 и GTX 285 AMP! Edition - 3D-ускорители для геймеров на новом GPU NVIDIA
09.01.2009  Net Applications: в декабре доли Firefox и Chrome росли за счет IE
09.01.2009  Imation говорит о «новом классе» SSD и первом в отрасли полном наборе для модернизации на основе SSD
09.01.2009  Маршрутизатор D-Link Xtreme N DIR-685 может играть роль NAS, сервера печати... и цифровой фоторамки
09.01.2009  Очень тонкая фотокамера Pentax Optio P70 имеет разрешение 12 Мп
09.01.2009  pureSilicon 1TB Nitro - первый в мире 2,5-дюймовый SSD объемом 1 ТБ
09.01.2009  Дебютировали мобильные GPU ATI Mobility Radeon HD 4000
09.01.2009  NVIDIA GeForce GTX 285 и GTX 295 представлены официально
09.01.2009  Scythe выпустила процессорный кулер Mugen 2
09.01.2009  Optio E70 - новая компактная камера Pentax начального уровня
09.01.2009  Новый iPhone получит четырехъядерный процессор?
08.01.2009  FreeBSD 7.1-RELEASE -- обновление операционной системы
08.01.2009  Появилась сборка Om 2008.12 для Neo FreeRunner от bytestore
 
Полезно

 
Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.
 
Rambler's Top100