Сущность технологии COM

2bbc099f

Моникеры и сохраняемость


Обсуждение моникеров не может быть полным без обсуждения файлового моникера (File Moniker). Напомним, что СОМ предусматривает три примитива активации: привязывание к объектам класса, привязывание к новым экземплярам класса и привязывание к постоянным объектам, хранящимся в файлах. В данной главе детально анализировались первые два из этих примитивов. Третий примитив основан на API-функции СОМ CoGetInstanceFromFile:

HRESULT CoGetInstanceFromFile( [in, unique] COSERVERINFO *pcsi, // host/security info - информация о хосте/безопасности [in, unique] CLSID *pClsid, // explicit CLSID (opt) - явный CLSID (opt) [in, unique] IUnknown *punk0uter, // for aggregation - для агрегирования [in] DWORD dwClsCtx, // locality? - локализация? [in] DWORD grfMode, // file open mode - режим открытия файла [in] OLECHAR *pwszName, // file name of object - файловое имя объекта [in] DWORD cmqi, // how many interfaces? - сколько интерфейсов? [out, size_is(cmqi)] MULTI_QI *prgmq // where to put itfs - куда поместить интерфейсы );

Эта функция принимает на вход имя файла, которое относится к постоянному состоянию (persistent state) объекта. CoGetInstanceFromFile удостоверяется в том, что объект исполняется, после чего возвращает один или несколько интерфейсных указателей на (ре)активированный объект. Чтобы выполнить эту работу, CoGetInstanceFromFile в первую очередь требуется определить CLSID данного объекта. CLSID требуется по двум причинам. Если объект не исполняется, то CLSID необходим СОМ для создания нового экземпляра, который будет инициализирован от постоянного (находящегося в файле) образа. Во-вторых, если вызывающий объект не указывает определенное хост-имя до запроса на активацию, то СОМ будет использовать CLSID для выяснения того, на какой машине следует активировать объект.

Если CLSID не передается явно вызывающим объектом, то CoGetInstanceFromFile извлекает CLSID из самого файла с помощью вызова API-функции СОМ GetClassFile:

HRESULT GetClassFile([in, string) OLECHAR *pwszFileName, [out] CLSID *pclsid);


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

После того как определены класс и хост-машина, СОМ исследует ROT (Running Object Table — таблица исполняющихся объектов) на целевой хост-машине для того, чтобы выяснить, является ли объект уже активированным. Таблица ROT является инструментом SCM, который преобразует произвольные моникеры в экземпляры объектов, исполняющихся на локальной хост-машине. Ожидается, что постоянные объекты будут регистрировать себя в локальной ROT во время загрузки. Чтобы представить файловое имя постоянного объекта в качестве моникера, СОМ предусматривает стандартный тип моникера — файловый моникер, который оборачивает имя файла в интерфейс IMoniker. Файловые моникеры могут создаваться либо путем передачи файлового имени в МkParseDisplayName, либо вызовом явной API-функции CreateFileMoniker:

HRESULT CreateFileMoniker( [in, string] const OLECHAR *pszFileName, [out] IMoniker **ppmk);

Если постоянный объект уже зарегистрировал в ROT свой файловый моникер, то CoGetInstanceFromFile просто возвращает указатель на уже работающий объект. Если же объект в ROT не найден, то СОМ создает новый экземпляр файлового класса и инициализирует его из постоянного объекта с помощью метода IPersistFile::Load этого экземпляра:

[object, uuid(0000010b-0000-0000-C000-000000000046)] interface IPersistFile : IPersist { // called by CoGetInstanceFromFile to initialize object // вызывается функцией CoGetInstanceFromFile для // инициализации объекта HRESULT Load( [in, string] const OLECHAR * pszFileName, [in] DWORD grfMode ); // remaining methods deleted for clarity // остальные методы удалены для ясности }

Реализация объекта отвечает за загрузку из файла всех постоянных элементов и за саморегистрацию в локальной таблице ROT — с целью убедиться, что для каждого файла в каждый момент может исполняться только один экземпляр:

STDMETHODIMP::Load(const OLECHAR *pszFileName, DWORD grfMode) { // read in persisted object state // считываем сохраненное состояние объекта HRESULT hr = this->MyReadStateFromFile(pszFile, grfMode); if (FAILED(hr)) return hr; // get pointer to ROT from SCM // берем указатель на ROT от SCM IRunningObjectTable *prot = 0; hr = GetRunningObjectTable(0, &prot); if (SUCCEEDED(hr)) { // create a file moniker to register in ROT // создаем файловый моникер для регистрации в ROT IMoniker *pmk = 0; hr = CreateFileMoniker(pszFileName, &pmk); if (SUCCEEDED(hr)) { // register self in ROT // саморегистрация в ROT hr = prot->Register(0, this, pmk, &m_dwReg); pmk->Release(); } prot->Release(); } return hr; }





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

Существование File Moniker обусловлено двумя причинами. Во-первых, он нужен, чтобы позволить объектам самим регистрироваться в ROT, чтобы их мог найти CoGetInstanceFromFile. Во-вторых, чтобы скрыть от клиента использование CoGetInstanceFromFile за интерфейсом IMoniker. Реализация File Moniker из BindToObject просто вызывает CoGetInstanceFromFile:

// pseudo-code from OLE32.DLL // псевдокод из OLE32.DLL

STDMETHODIMP FileMoniker::BindToObject(IBindCtx *pbc, IMoniker *pmkToLeft, REFIID riid, void **ppv) { // assume failure - на случай сбоя *ppv = О; HRESULT hr = E_FAIL; if (pmkToLeft == 0) { // no moniker to left - слева моникеров нет MULTI_QI mqi = { &riid, 0, 0 }; COSERVERINFO *pcsi; DWORD grfMode; DWORD dwClsCtx; // these three parameters are attributes of the BindCtx // эти три параметра являются атрибутами BindCtx this->MyGetFromBindCtx(pbc, &pcsi, &grfMode, &dwClsCtx); hr = CoGetInstanceFromFile(pcsi, 0, 0, dwClsCtx, grfMode, this->m_pszFileName, 1, &mqi); if (SUCCEEDED(hr)) *ppv = mqi.pItf; } else { // there's a moniker to the left - слева есть моникер // ask object to left for IClassActivator // or IClassFactory // запрашиваем объект слева от IClassActivator или от // IClassFactory } return hr; }

При таком поведении File Moniker следующая функция, вызывающая CoGetInstanceFromFile

HRESULT GetCornelius(IApe * &rpApe) { OLECHAR *pwszObject = OLESTR("\\\\server\\public\\cornelius.chmp"); MULTI_QI mqi = { &IID_IApe, 0, 0 }; HRESULT hr = CoGetInstanceFromFile(0, 0, 0, CLSCTX_SERVER, STCM_READWRITE, pwszObject, 1, &mqi); if (SUCCEEDED(hr)) rpApe = mqi.pItf; else rpApe = 0; return hr; }



может быть упрощена, если вместо этого использовать вызов CoGetObject:

HRESULT GetCornelius(IApe * &rpApe) { OLECHAR *pwszObject = OLESTR("\\\\server\\public\\cornelius.chmp"); return CoGetObject(pwszObject, 0, IID_IApe, (void**)&rpApe); }

Как и в предыдущем случае, когда использовался Class Moniker, уровень изоляции, обеспеченный CoGetObject, позволяет клиенту задавать сколь угодно сложную стратегию активации, не меняя ни единой строки кода.

1

Альтернативная версия этой API-функции. CoGetInstanceFromIStorage, вместо имени файла принимает указатель на иерархическое хранилище (storage medium).

2

В дополнение к обычной переадресации CLSID на хост-машины, которое используется функциями CoGetClassObject/CoCreateInstanceEx, CoGetInstanceFromFile может использовать в качестве имени файла UNC-имя хоста (universal naming convention — общее соглашение по именам), чтобы переадресовать запрос на активацию на ту хост-машину, где расположен данный файл. Этот режим активации упоминается в Спецификации СОМ как побитовая активация ("AtВits" activation) и описывается с использованием установок реестра "ActivateAtStorage", как описано в главе 6.

3

На практике областью действия ROT является не вся машина, а только Winstation. Это означает, что по умолчанию не все зарегистрированные сессии (logon sessions) получат доступ к объекту. Чтобы убедиться, что объект является видимым для всех возможных клиентов, при вызове IRunningObjectTable::Register объект должен выставить флаг ROTFLAGS_ALLOWANYCLIENT.


Содержание раздела