Сообщений 0    Оценка 316        Оценить  
Система Orphus

Моникеры.

Альтернативный путь создания объектов

Автор: Виктор Шарахов
The RSDN Group

Источник: RSDN Magazine #2
Опубликовано: 26.03.2003
Исправлено: 10.12.2016
Версия текста: 1.0
Введение
Что такое моникер?
Интерфейс IMoniker
Контекст связывания
Параметр pmkToLeft
Использование моникеров
Классификация моникеров
Композитный моникер (Generic composite moniker)
Моникер элемента (Item moniker)
Антимоникер (Antimoniker)
Моникер указателя (Pointer moniker)
Моникер ссылки (Objref moniker)
Моникер класса (Class moniker)
Файловый моникер (File moniker)
URL моникер (URL moniker)
Связывание моникера
Синхронное связывание
Асинхронное связывание
Создание моникеров
Разбор строк
Фабрика моникеров
Стратегия MkParseDisplayName
Стратегия MkParseDisplayNameEx
Примеры пользовательских моникеров
Моникер объекта, уже зарегистрированного в ROT
Моникер объектов управляемых типов .NET

Введение

Вспомним обычный, рекомендуемый СОМ, сценарий создания объектов с помощью CLSID:

IClassFactory* pCF;
// Создание фабрики класса, определяемого CLSID_object
hr = CoGetClassObject(CLSID_object, CLSCTX_SERVER, NULL, IID_IClassFactory, (void**) &pCF);
if( SUCCEEDED(hr) )
{
  // Создание объекта и запрос интерфейса IID_object
  hr = pCF->CreateInstance(NULL, IID_object, (void**) &pobject);
  pCF->Release();
}

Или более короткий и знакомый вариант, скрывающий детали предыдущего способа:

CoCreateInstance(CLSID_object, NULL, CLSCTX_SERVER, IID_object, (void**) &pobject);

Отметим два характерных момента, которые присутствуют в предложенных вариантах: обязательное знание CLSID объекта и обязательное создание вспомогательного объекта, так называемой фабрики класса, которая умеет создавать нужный нам объект. При этом возможны различные способы получения CLSID в программе клиента, например:

Для некоторых клиентов этого более чем достаточно. Однако существует альтернативный способ создания объектов без знания CLSID объекта и без прямого использования фабрики класса. Основная идея этого способа - вынесение механизма определения CLSID и активизации объекта этого класса во вспомогательный программный код за пределы клиента. Этот программный код и определяется моникерами.

Что такое моникер?

Моникером называют СОМ-объект, реализующий интерфейс IMoniker и позволяющий клиенту получить указатель на объект, идентифицируемый этим моникером, через вызов метода IMoniker::BindToObject. По имени этого метода процесс получения объекта от моникера называют связыванием моникера или активизацией объекта.

Чтобы уметь найти и активизировать свой объект, моникер должен обладать определенной информацией. Эту информацию моникер получает один раз (во время своего создания) в виде строки, формат которой он понимает. Впоследствии эта информация уже не может быть изменена. Поэтому моникер, будучи однажды созданным, всегда находит и активизирует один и тот же объект.

Строку инициализации моникера часто также называют моникером. Мы тоже на протяжении статьи для простоты будем называть строки моникерами, помня о том, что из строки можно всегда создать реальный моникер. Например, строка "C:/Documents/Project/Report.doc" может быть названа моникером, а точнее, файловым моникером, потому что с ее помощью можно легко создать реальный объект – файловый моникер.

Строка инициализации и постоянство моникера обеспечивают дополнительную функциональность моникеров – именование, или идентификацию, объектов в СОМ. Действительно, строка инициализации обеспечивает получение строго определенного моникера, а этот моникер, в свою очередь, – активизацию своего объекта, следовательно, ЭТА строка именует именно ЭТОТ объект. Благодаря такой функциональности моникеры используются, например, в web-страничках для ссылки на другие ресурсы сети.

Интерфейс IMoniker

Рассмотрим состав методов интерфейса IMoniker.

BindToObject – функция активизации объекта, идентифицируемого моникером (см. Связывание моникера).

BindToObject(IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riid, void* *ppvObj)

BindToStorage – функция активизации хранилища (storage), содержащего состояние объекта, идентифицируемого моникером. В отличие от BindToObject, сам объект не активизируется.

BindToStorage(IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riid, void* *ppvObj)

Reduce – сервисная функция получения сокращенного (или более эффективного) моникера, идентифицирующего тот же самый объект. Например, файловый моникер "C:/Documents/Project1/Report1.doc/../../Project2/Report2.doc" имеет более короткую форму в виде файлового моникера "C:/Documents/Project2/Report2.doc". Заметим, что файловый моникер, как и файловая система в целом, не различает имена каталогов и файлов при выполнении операции ".." (взятие родительского каталога).

Reduce(IBindCtx* pbc, DWORD dwHowFar, IMoniker* *ppmkToLeft,
       IMoniker* *ppmkReduced)

ComposeWith – сервисная функция объединения данного моникера с моникером, переданным в pmkRight. В зависимости от параметра fOnlyIfNotGeneric результатом объединения может быть или новый композитный моникер (см. Композитный моникер), или моникер такого же типа, что и данный моникер, если он может быть создан при объединении. Например, файловый моникер "C:/Documents/Project1/Report1.doc", объединяясь с файловым моникером "../../Project2/Report2.doc", вернет файловый моникер "C:/Documents/Project2/Report2.doc", но, объединяясь с моникером элемента "!Item", может вернуть только композитный моникер "C:/Documents/Project1/Report1.doc!Item".

ComposeWith(IMoniker* pmkRight, BOOL fOnlyIfNotGeneric,
            IMoniker* *ppmkComposite)
ПРИМЕЧАНИЕ

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

Enum – сервисная функция, возвращающая список моникеров, составляющих данный моникер, если они есть, конечно.

Enum(BOOL fForward, IEnumMoniker* *ppenum)

IsEqual – сервисная функция сравнения моникеров. Эта функция чувствительна к полной и сокращенной форме моникера.

IsEqual([in] IMoniker* pmkOther)
ПРИМЕЧАНИЕ

Таблица Работающих Объектов (ROT, Running Object Table) для сравнения моникеров запрашивает у них интерфейс IROTData и вызывает метод GetComparisonData этого интерфейса. Поэтому моникеры, регистрирующиеся в ROT, обязаны реализовать этот интерфейс.

Hash – сервисная функция вычисления 32-битного хеша моникера.

Hash(DWORD* pdwHash)

IsRunning – сервисная функция, позволяющая определить, активен ли в данный момент объект, идентифицируемый моникером.

IsRunning(IBindCtx* pbc, IMoniker* pmkToLeft, IMoniker* pmkNewlyRunning)

GetTimeOfLastChange – сервисная функция, позволяющая определить время последнего изменения объекта, идентифицируемого моникером.

GetTimeOfLastChange(IBindCtx* pbc, IMoniker* pmkToLeft, FILETIME* pFileTime)

Inverse – сервисная функция, позволяющая получить моникер, инверсный для данного моникера. То есть композитный моникер из данного и получившегося моникеров не идентифицирует никакой объект, следовательно, такой композитный моникер может быть исключен из процесса связывания. Как правило, из этой функции моникер возвращает просто антимоникер (см. Антимоникер). Но, например, композитный моникер возвращает новый композитный моникер из инверсных моникеров каждого моникера в исходном композите. Так, композитный моникер "C:/Documents/Project/Report.doc!Item!SubItem" вернет инверсный моникер в виде композитного моникера из трех антимоникеров "/../../..".

Inverse(IMoniker* *ppmk)

CommonPrefixWith – сервисная функция, позволяющая получить моникер, являющийся общей частью для данного моникера и моникера, переданного в pmkOther. Например, файловые моникеры "C:/Documents/Project1/Report1.doc" и "C:/Documents/Project2/Report2.doc" имеют общую часть в виде файлового моникера "C:/Documents". СОМ предоставляет системную функцию MonikerCommonPrefixWith, упрощающую реализацию этой функции.

CommonPrefixWith(IMoniker* pmkOther, IMoniker* *ppmkPrefix)

RelativePathTo – сервисная функция, позволяющая получить моникер, являющийся относительной формой моникера, переданного в pmkOther. То есть композитный моникер из данного и получившегося моникеров идентифицирует тот же объект, что и моникер pmkOther. Например, файловый моникер "C:/Documents/Project2/Report2.doc" относительно файлового моникера "C:/Documents" будет представлен файловым моникером "Project2/Report2.doc", а относительно "C:/Documents/Project1" – "../Project2/Report2.doc". СОМ предоставляет системную функцию MonikerRelativePathTo, упрощающую реализацию этой функции.

RelativePathTo(IMoniker* pmkOther, IMoniker* *ppmkRelPath)

GetDisplayName – функция получения символьной строки, которая представляет данный моникер в удобном для чтения виде. Хотя и не гарантируется, что из этой строки можно снова получить этот же моникер через универсальный разбор строки (см. Разбор строк), исключения крайне редки и связаны со специфическими требованиями исходного моникера. Например, моникер элемента имеет символьное представление "!Item", которое не переводится обратно в моникер элемента, потому что требует наличия моникера контейнера впереди него (или слева), например, в таком виде "C:/Documents/Project/Report.doc!Item".

GetDisplayName(IBindCtx* pbc, IMoniker* pmkToLeft,
               LPOLESTR* ppszDisplayName)

ParseDisplayName – функция разбора символьной строки. Моникер разбирает переданную строку самостоятельно или с помощью идентифицируемого объекта и возвращает новый моникер для распознанной части строки (см. Фабрика моникеров). Эта функция вызывается для моникера объекта, чтобы он вернул моникер подобъекта своего объекта. Например, при разборе строки "C:/Documents/Project/Report.doc!Item!SubItem" файловому моникеру, созданному по подстроке "C:/Documents/Project/Report.doc", будет передана нераспознанная часть "!Item!SubItem" исходной строки для получения новых моникеров. В данном случае, файловый моникер с помощью своего объекта (а это документ Word) вернет моникер элемента "!Item", которому в свою очередь будет передана для разбора строка "!SubItem".

ParseDisplayName(IBindCtx* pbc, IMoniker* pmkToLeft,
                 LPOLESTR pszDisplayName, ULONG* pchEaten,
                 IMoniker* *ppmkOut)

IsSystemMoniker – функция определения типа данного моникера (см. Классификация моникеров).

IsSystemMoniker(DWORD* pdwMksys)

Контекст связывания

Почти во все методы моникеров в качестве параметра передается указатель на контекст связывания – специальный СОМ-объект, реализующий интерфейс IBindCtx. Этот объект хранит информацию о том, каким образом должен проходить и как проходит в данный момент процесс связывания. И клиент, использующий моникеры, и сами моникеры, вовлеченные в процесс связывания, могут считывать и записывать эту информацию. Часть этой информации представлена системными структурами BIND_OPTS и BIND_OPTS2 и содержит, например, тайм-аут, режимы открытия файлов, параметры безопасности и так далее. В контексте обязательно будут сохраняться промежуточные объекты, создаваемые в процессе связывания, как в целях гарантии их существования в течение этого процесса, так и для оптимизации всех последующих вызовов, использующих этот же самый контекст. Таким образом, контекст связывания – это временный объект для хранения и передачи информации между вызовами разных методов разных объектов.

СОМ предоставляет специальную функцию CreateBindCtx, создающую новый контекст с заполненной структурой BIND_OPTS2:

cbStruct = sizeof(BIND_OPTS2);
grfFlags = 0;
grfMode = STGM_READWRITE;
dwTickCountDeadline = 0;
dwTrackFlags = 0;
dwClassContext = CLSCTX_SERVER;
locale = GetUserDefaultLCID();
pServerInfo = NULL;

При необходимости эти параметры контекста можно заменить другими при помощи функции IBindCtx::SetBindOptions.

Приведем пример использования контекста между вызовами функций MkParseDisplayName и BindToObject. В качестве примера попробуем реализовать СОМ-функцию CoGetObject:

WINOLEAPI CoGetObject(LPCWSTR pszName, BIND_OPTS* pBindOptions,
                      REFIID riid, void* *ppvObj)
{
 IbindCtx* pbc;
  HRESULT hr = CreateBindCtx(0,&pbc);
  if( SUCCEEDED(hr) )
  {
    if( pBindOptions )
      pbc->SetBindOptions(pBindOptions);

    ULONG chEaten;
    IMoniker* pmk;
    hr = MkParseDisplayName(pbc, pszName, &chEaten, &pmk);
    if( SUCCEEDED(hr) )
    {
      hr = pmk->BindToObject(pbc, NULL, riid, ppvObj);
      pmk->Release();
    }
    pbc->Release();
  }
  return hr;
}

Параметр pmkToLeft

Почти во все методы моникеров в качестве параметра передается указатель на моникер, стоящий слева (или впереди) от данного.

Как правило, клиент моникера устанавливает этот параметр равным NULL (см. предыдущий пример реализации CoGetObject), как признак того, что данный моникер не зависит от других, то есть сам должен определить свой объект. В этом случае моникер обязан проводить оптимизирующие действия, использующие контекст, ROT или другие способы.

Если этот параметр не равен NULL, моникер обязан кооперироваться с моникером pmkToLeft для получения своего объекта. Фактически, моникер pmkToLeft и данный моникер образуют неявный композитный моникер. Например, методы файлового моникера "../../Project2/Report2.doc" могут быть вызваны с моникером "C:/Documents/Project1/Report1.doc" в качестве параметра без дополнительного создания композитного моникера.

Использование моникеров

Типичным сценарием использования моникера является связывание моникера, создаваемого по имеющейся строке:

IBindCtx* pbc;
// Создание контекста связывания
CreateBindCtx(0,&pbc);
IMoniker* pmk;
// Создание моникера, определяемого "object_string"
hr = MkParseDisplayName(pbc, COLESTR("object_string"), NULL, &pmk);
if( SUCCEEDED(hr) )
{
  // Активизация объекта и запрос интерфейса IID_object
  hr = pmk->BindToObject(pbc, NULL, IID_object, (void**) &pobject);
  pmk->Release();
}
pbc->Release();

Или более короткий вариант, скрывающий детали предыдущего способа:

CoGetObject(L"object_string", NULL, IID_object, (void**) &pobject);

Рассмотрим особенности способов получения объектов через моникеры и через фабрики классов:

Некоторые из этих особенностей можно расценивать по-разному: как преимущества, или как недостатки, в зависимости от ситуации или пристрастий читателя.

Классификация моникеров

Поскольку интерфейс у моникеров один, они различаются только реализацией этого интерфейса. COM предоставляет несколько готовых к употреблению реализаций моникеров. Эти моникеры называются стандартными (или системными) моникерами. В СОМ существуют специальные функции для их создания. Стандартные моникеры в методе IsSystemMoniker возвращают код S_OK и ненулевое значение в своем единственном параметре pdwMksys.

Стандартные моникеры были спроектированы как моникеры универсального назначения и могут использоваться самостоятельно. Например, файловый моникер может быть использован для работы с именами файлов.

Пользовательские моникеры – это моникеры сторонних разработчиков. В методе IsSystemMoniker они обязаны возвращать код S_FALSE и значение MKSYS_NONE (0) в параметре pdwMksys.

Композитный моникер (Generic composite moniker)

Этот моникер служит для объединения в определенном порядке нескольких произвольных моникеров в один. Хотя в СОМ существуют функции объединения только двух моникеров, операция объединения моникеров распространяется на произвольное количество моникеров многократным применением этих функций, потому что объединение является ассоциативной операцией. То есть, если обозначить операцию объединения символом "Comp", то для произвольных моникеров A, B и C справедливы равенства:

Comp(Comp(A,B),C) = Comp(A,Comp(B,C)) = Comp(A,B,C)

Композитный моникер особо выделяет свой самый правый моникер (в приведенных выше равенствах таким будет моникер C), которому он переадресует все свои вызовы, передавая в качестве параметра pmkToLeft композитный моникер из всех оставшихся моникеров.

Композитный моникер в функции IsSystemMoniker возвращает код S_OK изначение MKSYS_GENERICCOMPOSITE (1) в параметре pdwMksys. Для создания композитного моникера существует специальная системная функция:

CreateGenericComposite(IMoniker* pmkFirst, IMoniker* pmkRest,
                       IMoniker* *ppmkComposite),

где pmkFirst - моникер, который должен стоять первым, pmkRest - моникер, который должен стоять в конце. В процессе выполнения функции результат по возможности упрощается. Функция IMoniker::ComposeWith может быть также использована для создания композитного моникера.

Пример композитного моникера, состоящего из файлового моникера "C:/Documents/Project1/Report1.doc" и двух моникеров элемента "!Item" и "!SubItem", – "C:/Documents/Project1/Report1.doc!Item!SubItem".

Моникер элемента (Item moniker)

Этот моникер служит для идентификации объекта в некотором контейнере. Моникер элемента может идентифицировать объекты, меньшие, чем файл, например, объекты, встроенные в составной документ, или псевдо-объекты (например, наборы клеток в таблице).

ПРИМЕЧАНИЕ

Единственное ограничение – моникер элемента всегда требует от предыдущего моникера-контейнера связывания с объектом, имеющим интерфейс IOleItemContainer.

Моникер элемента в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_ITEMMONIKER (4) в параметре pdwMksys. Для создания моникера элемента существует специальная системная функция:

CreateItemMoniker(LPCOLESTR lpszDelim, LPCOLESTR lpszItem, IMoniker* *ppmk),

где lpszDelim – разделитель в текстовом представлении (display name), lpszItem – имя объекта.

Пример моникера элемента – "!Item".

Антимоникер (Antimoniker)

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

Антимоникер в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_ANTIMONIKER (3) в параметре pdwMksys. Для создания антимоникера существует специальная системная функция:

CreateAntiMoniker(IMoniker* *ppmk).

Для создания антимоникера может быть также использована функция IMoniker::Inverse.

Антимоникер имеет единственное символьное представление – "..". Однако можно создать моникер "..", который не будет являться антимоникером. При объединении файлового моникера, допустим, "C:/Documents/Project1", с файловым моникером ".." образуется файловый моникер "C:/Documents". А при объединении с антимоникером файловый моникер уничтожается (см. Композитный моникер).

ПРИМЕЧАНИЕ

Часто спрашивают: "А зачем нужны антимоникеры?" Ответ: "Чтобы определить моникер объекта, для которого объект, идентифицируемый данным моникером, является подобъектом". Если бы дело касалось просто объектов, достаточно было бы вызвать метод объекта GetParent или подобный. У моникеров такой функциональности нет. Поэтому простой способ – композиция данного моникера с антимоникером – даст нам моникер, идентифицирующий "родительский" объект.

Моникер указателя (Pointer moniker)

Этот моникер служит для идентификации объекта в ситуациях, когда имеется указатель на интерфейс реально существующего объекта, а должен быть передан указатель на интерфейс моникера.

Моникер указателя в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_POINTERMONIKER (5) в параметре pdwMksys. Для создания моникера указателя существует специальная системная функция:

CreatePointerMoniker(IUnknown* punk, IMoniker* *ppmk),

где punk – указатель на интерфейс реально существующего объекта.

Этот моникер не имеет символьного представления.

Моникер ссылки (Objref moniker)

Этот моникер является улучшением моникера указателя и служит для тех же целей. Он включает в себя информацию о машине сервера, процессе, в котором выполняется объект, и указателе на этот объект. Его отличие от моникера указателя в том, что он имеет текстовое представление и может быть сохранен и восстановлен из потока (stream). Однако создавать свой объект он по-прежнему не умеет и будет возвращать ошибку связывания при отсутствии в системе объекта, на который он ссылается.

Моникер ссылки в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_OBJREFMONIKER (8) в параметре pdwMksys. Для создания моникера ссылки существует специальная системная функция:

CreateObjrefMoniker(IUnknown* punk, IMoniker* *ppmk),

где punk – указатель на интерфейс реально существующего объекта.

Пример моникера ссылки – "objref:TUVPVwEAAAAAAAAAAAAAAMAAAAAAAABGAQAAAAAAAACIKQAAhFHmKpwpAACEUeYqAQAAAOwEAACEBgAAAQAAACkAFgAHAHIAaQBvAAAABwAxADcAMgAuADEANQAuADEAMAAwAC4AMQA3ADgAAAAAAAkA//8AABAA//8AAAoA//8AAA4A//8AABEA//8AABIA//8AAAAA:" для некоторого созданного объекта MSComCtl2.DTPicker.2.

Моникер класса (Class moniker)

Этот моникер служит для идентификации фабрики класса и часто используется в связке с другими моникерами. Например, файловый моникер "C:/Documents/Project/Report.doc" активизирует документ приложения Word.

ПРИМЕЧАНИЕ

Если моникер класса имеет моникер слева от себя, то он требует от этого моникера связывания с объектом, имеющим интерфейс IClassActivator.

Моникер класса в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_CLASSMONIKER (7) в параметре pdwMksys. Для создания моникера класса существует специальная системная функция:

CreateClassMoniker(REFCLSID rclsid, IMoniker* *ppmk),

где rclsid – CLSID класса.

Пример моникера класса – "clsid:a7b90590-36fd-11cf-857d-00aa006d2ea4:".

Файловый моникер (File moniker)

Этот моникер служит для идентификации объекта, хранящего свои персистентные данные (persistent data) в файле. При связывании файловый моникер создает экземпляр COM-объекта, состояние которого хранится в файле, на который указывает моникер, запрашивает у объекта интерфейс IPersistFile и загружает состояние объекта из файла. Если необходимо идентифицировать объект, представляющий собой часть файла, требуется использовать композитный моникер, состоящий из файлового моникера и моникера элемента.

ПРИМЕЧАНИЕ

Если файловый моникер имеет моникер слева от себя, то он требует от этого моникера связывания с объектом, имеющим интерфейс IClassFactory или IClassActivator.

Файловый моникер в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_FILEMONIKER (2) в параметре pdwMksys. Для создания файлового моникера существует специальная системная функция:

CreateFileMoniker(LPCOLESTR lpszPathName, IMoniker* *ppmk),

где lpszPathName – имя файла. Может быть представлено в полной форме – "x:/absolutepath/filename", в относительной форме – "relativepath/filename" и "../otherpath/filename", или даже универсальной (UNC) форме – "//server/sharepath/filename".

Пример файлового моникера – "C:/Documents/Project/Report.doc".

URL моникер (URL moniker)

Этот моникер, самый сложный из всех стандартных моникеров, служит для идентификации объекта, находящегося где-либо в пространстве Интернета. По своей природе URL моникер – асинхронный, поскольку доступ к ресурсам сети требует продолжительного времени. Взаимодействие URL моникера с Интернет происходит через асинхронные подключаемые протоколы (Asynchronous Pluggable Protocols), которые осуществляют для этого моникера всю работу по приему и передаче данных.

URL моникер в функции IsSystemMoniker возвращает код S_OK и значение MKSYS_URLMONIKER (6) в параметре pdwMksys. Для создания URL моникера существует специальная системная функция:

CreateURLMoniker(IMoniker* pmkContext, LPWSTR szURL, IMoniker* *ppmk),

где pmkContext – моникер контекста URL, а szURL – строка адреса.

Примеры URL моникера – "http://www.rsdn.com" или "mailto:mag@rsdn.com".

Связывание моникера

Связывание моникера – это процесс установления связи между клиентом и объектом, идентифицируемым моникером. Это основная функция моникера, ради которой он и создается. Для связывания моникера клиент вызывает у него функцию BindToObject или BindToStorage в зависимости от того, что ему нужно: указатель на сам объект (BindToObject) или указатель на его хранилище данных (BindToStorage).

Клиент может не иметь ни малейшего представления о том, какой тип моникера он использует, потому что через интерфейс IMoniker все моникеры выглядят одинаково. Зная, как вызывать IMoniker::BindToObject, клиент способен использовать моникеры для создания и инициализации объектов самых разных типов, каждый из которых может иметь абсолютно уникальные требования по созданию и инициализации. Инкапсуляция этих требований в моникере устраняет необходимость знания клиентом специфики процесса связывания и упрощает код создания и инициализации объектов.

После инициации клиентом процесса связывания дальнейшее течение определяется стратегией, заложенной в реализации функции BindToObject, а также той информацией, которую клиент положил в контекст связывания.

Синхронное связывание

В самом простом случае моникер не возвращает управление клиенту до тех пор, пока не активизирует объект, реализуя так называемое синхронное связывание.


Рисунок 1. Связывание моникера в синхронном режиме.

Этот процесс состоит из нескольких этапов (см. рисунок 1):

  1. Предположим, клиент тем или иным способом получил указатель на моникер. Далее клиент вызывает метод моникера BindToObject или BindToStorage, передавая в качестве параметра IID нужного ему интерфейса. Заметим, что клиент не передает никаких сведений о CLSID объекта. Информацию об объекте моникер получает из своих данных.
  2. Моникер активизирует свой объект и заставляет его проинициализироваться, например, загрузив перманентные данные через один из интерфейсов IPersist* или задействовав другой механизм инициализации. Алгоритм работы моникера целиком и полностью определяется его разработчиком.
  3. Моникер запрашивает указатель на переданный интерфейс у созданного объекта и возвращает его клиенту.
  4. Клиент, получив нужный интерфейс, может продолжить работу, вызывая методы объекта. А моникер, сделав свое дело, может быть уничтожен так же, как и обычный COM-объект, уменьшением значения его счетчика ссылок до нуля.

Асинхронное связывание

В связи с большими временными задержками, связанными со сложностью процесса активизации и инициализации объекта, а также с появлением объектов в Интернет, моникеры были переработаны и стали предоставлять дополнительный способ связывания, так называемый асинхронный режим. В этом случае моникер сразу возвращает управление клиенту, но продолжает взаимодействовать со своим объектом асинхронно, оповещая клиента о течении и завершении этого процесса.

Клиент, собирающийся использовать асинхронное связывание, должен реализовать дополнительную функциональность и информировать об этом асинхронный моникер. Чтобы узнать, является ли сам моникер асинхронным, нужно запросить у него специальный интерфейс IAsyncMoniker или вызвать функцию СОМ IsAsyncMoniker. Чтобы получить выгоду от использования асинхронного моникера, клиент должен реализовать специальный объект с интерфейсом IBindStatusCallback, через который клиент будет оповещаться о ходе и завершении процесса связывания, и зарегистрировать этот интерфейс в контексте связывания. В противном случае асинхронный моникер не будет выполнять свои действия в асинхронном режиме.


Рисунок 2. Связывание моникера в асинхронном режиме.

Этот процесс состоит из нескольких этапов (см. рисунок 2):

  1. Предположим, клиент тем или иным способом получил указатель на моникер и определил, что моникер поддерживает асинхронный режим. Чтобы задействовать этот режим, клиент создает объект, реализующий интерфейс IBindStatusCallback, и регистрирует его в контексте связывания через функцию RegisterBindStatusCallback. Затем клиент вызывает метод моникера BindToObject или BindToStorage, передавая в качестве параметра известный клиенту IID нужного ему интерфейса.
  2. Асинхронный моникер определяет, что клиент готов взаимодействовать асинхронно, создает поток выполнения процесса связывания и передает в него интерфейс клиента IBindStatusCallback, полученный из контекста. После этого моникер возвращает управление клиенту с кодом MK_S_ASYNCHRONOUS .
  3. Новый поток моникера создает вспомогательный объект, реализующий интерфейс IBinding, и оповещает клиента о начале процесса связывания вызовом метода IBindStatusCallback::OnStartBinding, передавая указатель на этот объект клиенту. Далее моникер осуществляет активизацию и инициализацию объекта, периодически оповещая клиента о ходе этого процесса. После завершения всех действий моникер оповещает клиента вызовом метода IBindStatusCallback::OnObjectAvailable, передавая в его параметрах указатель на интерфейс, нужный клиенту.
  4. Клиент, получив нужный интерфейс через событие OnObjectAvailable, может продолжить работу, вызывая методы полученного объекта.

Создание моникеров

Откуда же берутся моникеры? Как клиент получает указатель на интерфейс моникера? Существуют разные способы получения моникеров.

Во-первых, моникер можно получить от идентифицируемого им объекта непосредственно, если объект умеет создавать моникер, ссылающийся на него. Например, пользователь, работающий с электронной таблицей Excel, может выделить часть электронной таблицы и скопировать ее в буфер обмена. Впоследствии он может вставить эту часть в документ другого приложения с помощью специальной команды вставки. В данном случае Excel предоставляет буферу обмена, а также всем приложениям-пользователям этого буфера, перманентные данные моникера выделенной части электронной таблицы. Когда пользователь вставляет то, что выглядит как электронная таблица, приложение на самом деле сохраняет у себя моникер, ссылающийся на данную часть электронной таблицы.

Во-вторых, моникер можно получить через специальные функции создания стандартных моникеров, передав этим функциям необходимую информацию. Это возможно, если известно, какой именно тип моникера нужно создать. Например, зная, что некоторая строка является именем файла, можно вызвать функцию создания файлового моникера CreateFileMoniker и передать ей эту строку.

Наконец, моникер можно получить без знания его конкретного типа через универсальный механизм разбора строки на моникеры, где на входе – произвольная строка, а на выходе – соответствующий ей моникер. Универсальный перевод символьных строк в моникеры – это задача двух библиотечных функций MkParseDisplayName и MkParseDisplayNameEx. Обе эти функции выступают в качестве обобщенной альтернативы набору специализированных функций, подобных CreateFileMoniker. Ниже будет подробно рассмотрен процесс разбора строки этими функциями.

Разбор строк

Фабрика моникеров

Прежде чем рассмотреть процесс разбора строк, остановимся на одном интересном объекте, который принимает участие в этом процессе. Это фабрика моникеров – специальный СОМ-объект, обладающий интерфейсом IParseDisplayName и умеющий создавать моникеры по передаваемой ему строке определенного вида. Интерфейс IParseDisplayName состоит всего из одного метода:

ParseDisplayName(IBindCtx* pbc, LPOLESTR pszDisplayName,
                 ULONG *pchEaten, IMoniker* *ppmkOut).

Параметр pszDisplayName – это строка, которую нужно разобрать. Формат этой строки определяется разработчиком фабрики и может иметь произвольный вид. Фабрика разбирает максимально возможную часть этой строки и возвращает количество разобранных символов в pchEaten и соответствующий моникер в ppmkOut.

К услугам фабрик моникеров прибегают в своей работе и функция MkParseDisplayName, и сами моникеры при реализации функции IMoniker::ParseDisplayName.

Стратегия MkParseDisplayName

Задача функции MkParseDisplayName – полностью разобрать полученную строку и вернуть моникер, соответствующий этой строке. Стратегия этой функции заключается в определении начального моникера с последующей серией шагов по полному разбору строки. Такой шаг заключается в обращении к функции IMoniker::ParseDisplayName текущего моникера с параметром в виде оставшейся части строки и объединении текущего и полученного из функции моникеров в новый моникер, который будет текущим для следующего шага.

Сначала выделяется максимальная подстрока, состоящая из символов, допустимых в имени файла, и создается проверяемый файловый моникер. Затем функция запрашивает у файловой системы, существует ли в ней файл с таким именем. В случае отсутствия файла функция запрашивает у ROT, активен ли объект с таким файловым моникером, чтобы иметь возможность идентифицировать по имени файла те объекты, которые еще не были сохранены в файловой системе. Если проверка файлового моникера оказалась успешной, то он становится первым начальным моникером в процессе разбора строки.

Если проверка файлового моникера оказалась неудачной, осуществляется проверка принадлежности строки некоторому пространству имен (namespace), определяемому с помощью синтаксических правил или шаблонов имен. В настоящий момент функция MkParseDisplayName проверяет два пространства имен, имеющих следующие шаблоны (в скобках "[" и "]" показана необязательная часть):

Функция выделяет максимальную подстроку, состоящую из букв, цифр или точек, для части "<имя>" шаблона, и переводит ее в CLSID объекта через функцию CLSIDFromProgID. Если такое преобразование было успешным, то считается, что это CLSID фабрики моникеров. То есть у фабрики этого класса запрашивается интерфейс IParseDisplayName. В случае неудачи этот же интерфейс запрашивается у объекта этого класса. Если оказывается, что это действительно фабрика моникеров, то в функцию IParseDisplayName::ParseDisplayName этого интерфейса передается строка для получения первого начального моникера.

Если ни один из предыдущих шагов не привел к получению начального моникера, процесс разбора строки заканчивается с ошибкой.

ПРИМЕЧАНИЕ

Функции ParseDisplayName интерфейсов IParseDisplayName и IMoniker различаются коренным образом. Они вызываются в разных ситуациях и имеют дело с разными строками. IParseDisplayName::ParseDisplayName вызывается у фабрики моникеров, чтобы получить экземпляр моникера, создаваемого этой фабрикой. В этом случае строка должна содержать пространство имен. IMoniker::ParseDisplayName пытается прочесть максимально возможную часть строки, интерпретируя ее как строковый моникер подобъекта. Поэтому строка может иметь заранее неизвестный формат. Моникер, как правило, переадресует свой запрос в фабрику моникеров.

Пространство имен моникеров расширяемо, и достигается это созданием собственной фабрики моникеров, разбирающей строки своего пространства, и регистрацией этой фабрики под некоторым ProgID (см. Примеры пользовательских моникеров).

Стратегия MkParseDisplayNameEx

Стратегия MkParseDisplayNameEx отличается от стратегии MkParseDisplayName. Во-первых, осуществляя проверку выделенного "<имени>", она далее не использует фабрику моникеров, а создает для всей строки URL моникер, который осуществляет фактический разбор строки уже во время связывания. Во-вторых, выделенная часть "<имя>" дополнительно трактуется функцией MkParseDisplayNameEx, так же, как и URL моникером, не как имя фабрики моникеров, а как имя асинхронного встраиваемого протокола (Asynchronous Pluggable Protocols) из раздела Реестра HKCR\PROTOCOLS. Рассмотрение этих протоколов выходит за рамки данной статьи, но с точки зрения моникеров, они также определяют пространства имен.

Примеры пользовательских моникеров

Моникер объекта, уже зарегистрированного в ROT

В этом примере будет спроектирован моникер, который позволит клиенту получать ссылки на уже созданные экземпляры объектов, зарегистрированные в ROT. Точнее, даже не моникер, а просто фабрика моникеров, генерирующая стандартные моникеры указателя или ссылки, достаточные для идентификации объекта, получаемого с помощью функции GetActiveObject в фабрике моникеров.

Сначала необходимо ввести новое пространство имен. Назовем это пространство – "rot". Строковые моникеры для получения объекта будут выглядеть как "rot:{CLSID}" или "rot:ProgID". Затем нужно создать фабрику моникеров для выбранного пространства имен.

Для этого создадим ATL-проект, и в нем – простой ATL-объект (Simple COM Object). Его название может быть произвольным, обозначим его – x. Наведем некоторую косметику в этом объекте – для соответствия выбранному пространству имен.

Во-первых, нужно зарегистрировать кокласс под ProgID "rot", чтобы описать новое пространство имен. Для этого открываем файл x.rgs, который есть в проекте, вставляем в него выделенные строчки и копируем информацию о CLSID из описания Cx кокласса в описание rot:

HKCR
{
. . .
  ATLПроект.x = s 'что-то'
  {
    CLSID = s '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}'
    CurVer = s 'ATLПроект.x.1'
  }
  rot = s 'ROT Object Moniker Class'
  {
    CLSID = s '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}'
  }
. . .

Во-вторых, нам нужна фабрика моникеров. Ее можно создать, реализовав интерфейс IParseDisplayName в фабрике класса или в самом объекте.

В этом примере мы используем фабрику класса. Стандартная фабрика класса из ATL интерфейсом IParseDisplayName не обладает. Поэтому открываем файл x.h, который есть в проекте, и описываем новую фабрику класса CComClassFactoryMoniker, а в описание класса Cx вставляем ссылку на новую фабрику класса, т.е. DECLARE_CLASSFACTORY_EX(), таким образом:

        //////////////////////////////////////////////////
        // CComClassFactoryMoniker
        class
        CComClassFactoryMoniker : public CComClassFactory,
  publicIParseDisplayName
{
public:
  BEGIN_COM_MAP(CComClassFactoryMoniker)
    COM_INTERFACE_ENTRY(IClassFactory)
    COM_INTERFACE_ENTRY(IParseDisplayName)
  END_COM_MAP()
  // IParseDisplayName
  STDMETHOD(ParseDisplayName)(/*[in]*/ IBindCtx* pbc, 
                              /*[in]*/ LPOLESTR pszDisplayName,
                              /*[out]*/ ULONG* pchEaten, 
                              /*[out]*/ IMoniker* *ppmkOut)
  {
    if( !ppmkOut || !pchEaten )
      return E_POINTER;

    CLSID clsid;
    CComPtr<IMoniker> pmki;
    HRESULT hr = MK_E_SYNTAX;
    *ppmkOut = NULL;

    // строка будет "rot:{CLSID}" или "rot:ProgID"
    *pchEaten = wcslen(pszDisplayName);
    LPOLESTR psz1 = wcschr(pszDisplayName, ':');
    if( !psz1 )
      return hr;
    else
      psz1++; // пропускаем ':'// считаем, что в строке только наша информацияif( *psz1 != '{' )
    { // наверное, это ProgID
      hr = CLSIDFromProgID(psz1, &clsid);
    }
    else
    { // наверное, это {CLSID}
      hr = CLSIDFromString(psz1, &clsid); 
    }
    if( FAILED(hr) ) // увы, нет
    {
      // здесь можно попытаться трактовать как "rot:<другой моникер>"// например, "rot:C:\\file.doc"// ULONG pchEaten2 = psz1-pszDisplayName;// hr = MkParseDisplayName(pbc, psz1, pchEaten, ppmk);// pchEaten += pchEaten2; // if( *ppmk && ppmk[0]->IsRunning(pbc,NULL,NULL) != S_OK )// { // не активен объект у этого моникера//   ppmk[0]->Release();//   *ppmkOut = NULL;//   hr = MK_E_NOOBJECT;// }return hr;
    }

    CComPtr<IUnknown> pvi;
    hr = GetActiveObject(clsid, NULL, &pvi);
    if( hr == S_OK )
    {
      // надо бы CreateObjrefMoniker, но у меня нет такого// hr = CreateObjrefMoniker(pvi, ppmkOut);
      hr = CreatePointerMoniker(pvi, ppmkOut);
    }
    else
    { // здесь можно попытаться создать объект
      hr = MK_E_NOOBJECT;
    }
    return hr;
  }
};
//////////////////////////////////////////////////// Cxclass ATL_NO_VTABLE Cx : 
. . .
{ 
public: 
. . .
// Новая фабрика классаDECLARE_CLASSFACTORY_EX(CComClassFactoryMoniker)
// Сам объект класса не используетсяtypedef CComFailCreator<E_NOTIMPL> _CreatorClass;
. . .
};

Теперь остается скомпилировать проект и воспользоваться моникером "rot:{CLSID}" или "rot:ProgID".

Моникер объектов управляемых типов .NET

Данный моникер разработан Don Box и Jason Whittington в 2000 году в качестве иллюстрации и позволяет классическому COM-клиенту создавать объекты CLR непосредственно и без какой-либо необходимости в регистрации этих объектов в Реестре. Единственное требование: на машине, где используется моникер, должен быть установлен .NET Framework.

Сначала определяется пространство имен разрабатываемого моникера. Авторы решили, что это будет dm.net:['<assembly-name>']<class-name>, где скобки "[" и "]" требуются по синтаксису.

Затем проектируется и создается класс этого моникера на основе разработанного авторами шаблона моникеров CComMoniker<T>, обеспечивающего поддержку произвольного моникера, и в нем реализуются 3 функции: BindToObject, GetDisplayName и ParseDisplayName, которые не реализованы в основном шаблоне.

        //////////////////////////////////////////////////
        // CLRMon  implementation
        class ATL_NO_VTABLE CLRMon : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CLRMon, &CLSID_clrmon>,
  public CComMoniker<CLRMon>
{
. . .
CComBSTR m_bstrAssembly;
CComBSTR m_bstrClass;
CComBSTR m_bstrDisplayName;
. . .
BEGIN_COM_MAP(CLRMon)
  COM_INTERFACE_ENTRY(IMoniker)
  COM_INTERFACE_ENTRY(IPersist)
  COM_INTERFACE_ENTRY(IPersistStream)
  COM_INTERFACE_ENTRY(IParseDisplayName)
  COM_INTERFACE_ENTRY(IROTData)
  COM_INTERFACE_ENTRY(IMarshal)
END_COM_MAP()
. . .
staticvoid WINAPI ObjectMain(bool bStarting)
{
  if(bStarting)
    InitHost();
  else
    TermHost();
}
. . .
STDMETHODIMP BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft, 
    REFIID riidResult, void **ppvResult)
{
  if(LoadCORClass(m_bstrAssembly, m_bstrClass, riidResult, ppvResult) 
     != S_OK)
    return MK_E_NOOBJECT;
  elsereturn S_OK;
}

STDMETHODIMP GetDisplayName(IBindCtx *pbc, IMoniker *pmkToLeft, 
                            LPOLESTR *ppszDisplayName)
{
  // Оригинальный код.if(ppszDisplayName == NULL) return E_POINTER;
  if(*ppszDisplayName == NULL) return E_POINTER;
  *ppszDisplayName = m_bstrDisplayName;
  return E_FAIL;
  // По-моему, так делать не надо.// Out-параметр ppszDisplayName подразумевает, что в нем будет // возвращена строка, память под которую размещается с помощью // функции CoTaskMemAlloc. Здесь же это не так. Нужно что-то подобное:// if(ppszDisplayName == NULL || pmkToLeft != NULL)//   return E_POINTER;// ULONG cb = 2*(m_bstrDisplayName.Length()+1);// if (*ppszDisplayName = (LPOLESTR) CoTaskMemAlloc(cb)) == NULL)//   return E_OUTOFMEMORY;// memcpy(*ppszDisplayName, m_bstrDisplayName, cb);// return S_OK;
}

STDMETHODIMP ParseDisplayName(IBindCtx *pbc, IMoniker *pmkToLeft, LPOLESTR pszDisplayName,
                              ULONG *pchEaten, IMoniker **ppmkOut)
{
  if(pbc == NULL)
    return E_POINTER;
  if(pszDisplayName == NULL)
    return E_INVALIDARG;
  if(lstrlenW(pszDisplayName) == 0)
    return E_INVALIDARG;
  if(!m_bstrDisplayName) 
    m_bstrDisplayName = pszDisplayName;

  //Формат должен быть dm.net:['assembly']classconst OLECHAR * pnext = pszDisplayName;
  pnext = FindChar(pnext, L':');
  if(pnext == NULL) 
    return MK_E_SYNTAX;

  pnext++;
  if (*pnext++ != '[')
    return MK_E_SYNTAX;
  if (*pnext++ != '\'')
    return MK_E_SYNTAX;
  const OLECHAR *pp = FindChar(pnext, '\'');

  CComBSTR assm(pp - pnext, pnext);
  m_bstrAssembly.m_str = assm.Detach();
  pnext = pp + 1;
  if (*pnext++ != ']')
    return MK_E_SYNTAX;
  
  m_bstrClass = pnext;
  *pchEaten = lstrlenW(pszDisplayName);
  return GetUnknown()->QueryInterface(ppmkOut);
}
. . .
};

Полные исходные тексты этого примера вместе с проектом и описанием можно найти на http://staff.develop.com/jasonw/clr/readme.htm. Не со всем в них можно согласиться, но реальный пример моникера всегда полезен.


Эта статья опубликована в журнале RSDN Magazine #2. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 316        Оценить