Essential COM - page 44

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

В предыдущих примерах активации со стороны клиента осуществлялись явные вызовы 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 в MkParseDisplayName1:

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-функции СОМ CreateBindCtx2.

В 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. активация с использованием class moniker

Рисунок 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 из-за ограничений языка.


1 Хотя использование MkParseDisplayName будет несколько менее эффективным, оно обладает гораздо большей гибкостью. Как отмечалось ранее, отображаемое имя может быть прочитано из файла или даже из пользовательского интерфейса. Отличным примером такого приложения является Internet Explorer фирмы Microsoft, так как он позволяет пользователям набирать произвольные имена объектов (URL), которые превращаются в моникеры (с использованием расширенной API-функции MkParseDisplayNameEx).

2 Контексты связывания используются композитными моникерами для оптимизации операций синтаксического анализа и связывания. Кроме того, контексты связывания позволяют клиентам выставить флаги CLSCTX, а также COSERVERINFO, хотя текущая реализация Class Moniker проигнорирует оба эти атрибута. Вместо этого Class Moniker предполагает, что он будет скомпонован с тем моникером, который ссылается на реализацию интерфейса IClassActivator, допускающим намного большую гибкость.