Page #38 ( )

Активация

Клиентам требуется механизм для поиска объектов класса. В силу динамической природы СОМ это может привести к загрузке библиотеки DLL или запуску обслуживающего процесса (server process). Эта процедура вызова объекта к жизни называется активацией объекта.

В СОМ имеется три модели активации, которые можно использовать для занесения объектов в память, чтобы сделать возможными вызовы методов. Клиенты могут попросить СОМ связать объект класса с данным классом. Кроме того, клиенты могут попросить, чтобы СОМ создала новые экземпляры классов, определенные с помощью CLSID. Наконец, клиенты могут попросить СОМ вызвать к жизни перманентный (persistent) объект, состояние которого определено как постоянное. Из этих трех моделей только первая (связывание с объектом класса) является абсолютно необходимой. Две другие модели являются просто оптимизациями обычно применяющихся способов активации. Дополнительные, определенные пользователем, модели активации могут быть реализованы в терминах одного (или более) из этих трех примитивов.

рис. 3.1. библиотека сом и scm

Каждая из описанных трех моделей активации пользуется услугами имеющегося в СОМ диспетчера управления сервисами SCM (Service Control Manager)1. SCM является основной точкой рандеву для всех запросов на активацию в каждой отдельной машине. Каждая хост-машина, поддерживающая СОМ, имеет свой собственный локальный SCM, который переадресовывает удаленные запросы на активацию на SCM удаленной машины, где этот запрос будет трактоваться как локальный запрос на активацию. SCM используется только для того, чтобы активировать объект и привязать к нему начальный указатель интерфейса. Как только объект активирован, SCM более не связан с вызовом методов клиента и объекта. Как показано на рис. 3.1, под Windows NT SCM реализован в службе RPCSS (Remote Procedure Call Service System — система сервиса удаленного вызова процедур). Службы SCM объявляются в программы как высокоуровневые типы моникеров2 и как низкоуровневые API-функции, причем все они реализованы в библиотеке СОМ (как это называется в Спецификации СОМ). Под Windows NT большая часть библиотеки СОМ реализована в OLE32.DLL. Для повышения эффективности библиотека СОМ может использовать локальный или кэшированный режим, чтобы избежать ненужных запросов службы RPCSS со стороны IPC (interprocess communication — межпроцессное взаимодействие).

Напомним, что главным принципом СОМ является разделение интерфейса и реализации. Одной из деталей реализации, скрытых от клиента, является местонахождение реализации объекта. Невозможно определить, не только на каком хосте был активирован объект, но и был ли локальный объект активирован в клиентском процессе или в отдельном процессе на локальной машине. Это дает разработчикам объектов очень большую гибкость при решении того, как и где использовать реализации объектов, учитывая такие проблемы, как устойчивость к сбоям (robustness), обеспечение безопасности, распределение нагрузки и производительность. Клиент имеет возможность во время активации указать свои предпочтения относительно того, где будет активирован объект. Многие клиенты, однако, выражают свое безразличие к данному вопросу. В таком случае этот выбор сделает SCM, исходя из текущей конфигурации нужного класса.

Когда объект активирован внутри процесса, то в процесс клиента загружается та библиотека DLL, которая реализует методы объекта, и все данные-члены хранятся в адресном пространстве клиента. Так как не требуется никаких переключении процессов, то эффективность вызова методов чрезвычайно высока. Кроме того, клиентский поток может быть использован для прямого выполнения кода метода, при условии, что требования по организации поточной обработки (threading requirements) объекта соответствуют клиентским требованиям. Если у клиента и у объекта требования по организации поточной обработки совместимы, то также не нужно никаких переключении потоков. Если вызовы метода могут выполняться с использованием клиентского потока, после активации объекта не требуется участия никакой промежуточной среды времени выполнения, и цена вызова метода просто равна вызову виртуальной функции. Это обстоятельство делает СОМ, встроенный в процесс, особенно хорошо приспособленным для приложений, чувствительных к эффективности выполнения, так как вызов метода обходится не дороже, чем обычный вызов глобальной функции в DLL3.

Когда объект активирован извне процесса (то есть в другом процессе на локальной или удаленной машине), то код, реализующий методы объекта, выполняется в процессе определенного сервера и все данные-члены объекта сохраняются в адресном пространстве процесса сервера. Чтобы позволить клиенту связываться с внепроцессным (out-of-process) объектом, СОМ прозрачно (скрытно от клиента) возвращает ему "заместитель" (proxy) во время активации. В главе 5 подробно обсуждается, что этот "заместитель" выполняется в клиентском потоке и переводит вызовы метода, преобразованные в RPC-запросы (Remote Procedure Call — удаленный вызов процедуры), в контекст исполнения сервера, где эти RPC-запросы затем преобразуются обратно в вызовы метода текущего объекта. Это делает вызов метода менее эффективным, так как при каждом обращении к объекту требуются переключение потока и переключение процесса. К преимуществам внепроцессной (то есть работающей не в клиентском процессе) активации относятся изоляция ошибок, распределение и повышенная безопасность. В главе 6 внепроцессная активация будет рассматриваться подробно.


1 В Windows NT также имеется подсистема, известная как Service Control Manager, или просто Services, которая используется для запуска процессов, не зависящих от входа в систему. Далее в этой книге мы будем называть этот диспетчер управления сервисами NT SCM, чтобы отличать его от СОМ SCM.

2 Моникеры являются поисковыми объектами, которые скрывают детали активации или связывающего алгоритма. Более подробно моникеры обсуждаются далее в этой главе.

3 Степень изоляции, необходимая для вызова во внешнюю DLL, приблизительно эквивалентна вызову функции через вход таблицы vtbl.

Использование SCM

Напомним, что SCM поддерживает три примитива активации (связывание с объектами класса, связывание с экземплярами класса, связывание с постоянными экземплярами из файлов). Как показано на рис. 3.2, эти примитивы логически разделены на уровни1. Примитивом нижнего уровня является связывание с объектом класса. Этот примитив также наиболее прост для понимания.

Вместо того чтобы вручную загружать код класса, клиенты пользуются услугами SCM посредством низкоуровневой API-функции СОМ CoGetClassObject. Эта функция запрашивает SCM присвоить значение указателю на требуемый объект класса:

HRESULT CoGetClassObject(
  [in] REFCLSID rclsid,    // which class object?
                           // Какой объект класса? 
  [in] DWORD dwClsCtx,     // locality?
                           //местонахождение?
  [in] COSERVERINFO *pcsi, // host/security info 
                           //сведения о сервере и обеспечении безопасности 
  [in] REFIID riid,        // which interface?
                           // какой интерфейс?
  [out, iid_is(riid)] void **ppv); // put it here! 
                                   // поместим его здесь!

 

рис. 3.2. примитивы активации

Первый параметр в CoGetClassObject показывает, какой класс реализации запрашивается. Последний параметр — это ссылка на указатель интерфейса, с которым нужно связаться, а четвертый параметр — это просто IID, описывающий тип указателя интерфейса, на который ссылается последний параметр. Более интересные параметры — второй и третий, которые определяют, когда объект класса должен быть активирован.

В качестве второго параметра CoGetClassObject принимает битовую маску (bitmask), которая позволяет клиенту указать характеристики скрытого и живучего состояний объекта (например, будет ли объект запущен в процессе, вне процесса или вообще на другом сервере). Допустимые значения для этой битовой маски определены в стандартном перечислении CLSCTX:

enum tagCLSCTX {
  CLSCTX_INPROC_SERVER  = 0х1, // run -inprocess
                               // запуск в процесс 
  CLSCTX_INPROC_HANDLER = 0х2, // see note2
                               // смотрите сноску2
  CLSCTX_LOCAL_SERVER   = 0х4, // run out-of-process
                               // запуск вне процесса 
  CLSCTX_REMOTE_SERVER  = 0х10 // run off-host
                               // запуск вне хост-машины 
} CLSCTX;
 

Эти флаги могут быть подвергнуты побитному логическому сложению (bit-wise-ORed together), и в случае, когда доступен более чем один запрошенный CLSCTX, СОМ выберет наиболее эффективный тип сервера (это означает, что СОМ будет, когда это возможно, использовать наименее значимый бит битовой маски). Заголовочные файлы SDK также включают в себя несколько сокращенных макросов, которые сочетают несколько флагов CLSCTX, используемых во многих обычных сценариях:

#define CLSCTX_INPROC (CLSCTX_INPROC_SERVER | \ 
                       CLSCTX_INPROC_HANDLER)
#define CLSCTX_SERVER (CLSCTX_INPROC_SERVER | \
                       CLSCTX_LOCAL_SERVER | \
                       CLSCTX_REMOTE_SERVER) 
#define CLSCTX_ALL    (CLSCTX_INPROC_SERVER | \
                       CLSCTX_INPROC_HANDLER | \
                       CLSCTX_LOCAL_SERVER | \
                       CLSCTX_REMOTE_SERVER)

 

Заметим, что такие среды, как Visual Basic и Java, всегда используют CLSCTX_ALL, показывая тем самым, что подойдет любая доступная реализация.

Третий параметр CoGetClassObject — это указатель на структуру, содержащую информацию об удаленном доступе и безопасности. Эта структура имеет тип COSERVERINFO и позволяет клиентам явно указывать, какой машине следует активировать объект, а также как конфигурировать установки обеспечения безопасности, используемые при создании запроса на активацию объекта:

typedef struct _COSERVERINFO { 
  DWORD       dwReserved1; // reserved, must be zero
                           // зарезервировано, должен быть нуль 
  LPWSTR      pwszName;    // desired host name, or null
                           // желаемое имя хост-машины или нуль 
  COAUTHINFO *pAuthInfo;   // desired security settings
                           // желаемые установки безопасности 
  DWORD       dwReserved2; // reserved, must be zero
                           // зарезервировано, должен быть нуль
} COSERVERINFO;
 

Если клиент не указывает имя хоста (host name), а использует только флаг CLSCTX_REMOTE_SERVER, то для определения того, какая машина будет активировать объект, СОМ использует информацию о конфигурации каждого CLSID. Если клиент передает явное имя хоста, то оно получит приоритет перед любыми ранее сконфигурированными именами хостов, о которых может знать СОМ. Если клиент не желает передавать явную информацию о безопасности или имя хоста в CoGetClassObject, можно применить нулевой указатель COSERVERINFO.

Имея в наличии CoGetClassObject, клиент может дать запрос SCM на связывание указателя интерфейса с объектом класса:

HRESULT GetGorillaClass(IApeClass * &rpgc) 
{
    // declare the CLSID for Gorilla as a GUID 
    // определяем CLSID для Gorilla как GUID 
  const CLSID CLSID_Gorilla = { 0x571F1680, 0xCC83, 0x11d0,
        { 0x8C, 0х48, 0х00, 0х80, 0xС7, 0х39, 0x25, 0xBA } };
    // call CoGetClassObject directly 
    // вызываем прямо CoGetClassObject 
  return CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL, 0,
      IID_IApeClass, (void**)&rpgc);
}
 

Отметим, что если запрошенный класс доступен как внутрипроцессный сервер, то СОМ автоматически загрузит соответствующую DLL и вызовет известную экспортируемую функцию, которая возвращает указатель на требуемый объект класса3. Когда вызов CoGetClassObject завершен, библиотека СОМ и SCM полностью выходят из игры. Если бы класс был доступен только с внепроцессного или удаленного сервера, СОМ вместо этого возвратила бы заместитель, который позволил бы клиенту получить удаленный доступ к объекту класса.

Напомним, что интерфейс IApeClass придуман для того, чтобы позволить клиентам находить или создавать экземпляры заданного класса. Рассмотрим следующий пример:

HRESULT FindAGorillaAndEatBanana(long nGorillaID)
{
    IApeClass *pgc = 0;
      // find the class object via CoGetClassObject 
      // находим объект класса с помощью CoGetClassObject 
    HRESULT hr = CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL,
                                  0, IID_IApeClass, (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(CLSID_Gorilla, CLSCTX_ALL,
                                  0, IID_IApeClass, (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, эти примеры идентичны. Поскольку объекты класса могут экспортировать сколь угодно сложные интерфейсы, то их можно использовать для моделирования довольно изощренной стратегии активации, инициализации и размещения объектов.


1 Это разделение в значительной степени концептуально, так как библиотека СОМ и протокол передачи (wire-protocol) реализуют каждый примитив как отдельную ветвь программы и формат пакета.

2 Внутрипроцессные обработчики (in-process handlers) — в значительной степени пережитки документации OLE. Эти обработчики являются виутрипроцессными компонентами, выступающими в качестве представителей клиентской стороны объекта, который в действительности находится в другом процессе. Обработчики используются в документах OLE для кэширования изображений у клиента с целью сократить поток IPC (interprocess communication — межпроцессное взаимодействие) при перерисовке экрана. Хотя эти обработчики в общем случае производят считывание, они редко используются вне контекста документов OLE. Windows NT 5.0 будет обеспечивать дополнительные возможности для реализации обработчиков, но подробности того, как это будет достигнуто, были еще схематичны во время написания этой книги.

3 Требования параллелизма для класса должны технически соответствовать таким же требованиям в потоке вызова.