Изменить стиль страницы

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 , использующие то же файловое имя, не будут создавать новые объекты, а вместо этого возвратят ссылки на этот объект[3].

Существование 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, позволяет клиенту задавать сколь угодно сложную стратегию активации, не меняя ни единой строки кода.

Время жизни сервера

В предыдущих разделах было показано, как СОМ автоматически загружает DLL с целью перенесения реализации объектов в адресное пространство клиентских программ. Однако пока не обсуждалось, как и когда эти DLL выгружаются. Вообще говоря, серверные DLL могут предотвращать преждевременную выгрузку, но именно клиент выбирает момент, когда DLL фактически перестают использоваться. Клиенты, желающие освободить неиспользуемые DLL, вызывают API-функцию СОМ CoFreeUnusedLibraries:

void CoFreeUnusedLibraries(void);

Обычно эта подпрограмма вызывается клиентами в свободное время с целью собрать мусор в своем адресном пространстве. При вызове CoFreeUnusedLibraries СОМ опрашивает каждую из загруженных DLL, чтобы выявить, какие из них не используются. Это делается посредством вызова в каждой DLL функции DllCanUnloadNow, которая должна быть явно экспортирована из этой динамически подключаемой библиотеки.

Функция DllCanUnloadNow, которую экспортирует DLL каждого сервера, должна соответствовать следующей сигнатуре:

HRESULT DllCanUnloadNow(void);

Если DLL желает быть освобожденной, то она возвращает S_OK. Если DLL хочет остаться загруженной, то она возвращает S_FALSE. Серверные DLL должны оставаться загруженными по крайней мере до тех пор, пока сохраняются интерфейсные указатели на ее объекты. Это означает, что в DLL должен быть счетчик всех существующих ссылок на объекты. Чтобы упростить реализацию этого, большинство DLL содержат одну переменную для счетчика блокировок (lock count) и используют две функции для автоматического инкрементирования и декрементирования этого счетчика:

LONG g_cLocks = 0; void LockModule(void)

{ InterlockedIncrement(&g_cLocks); }

void UnlockModule(void)

{ InterlockedDecrement(&g_cLocks); }

При наличии этих подпрограмм реализация DllCanUnloadNow становится чрезвычайно простой:

STDAPI DllCanUnloadNow(void)

{ return g_cLocks == 0 ? S_OK : S_FALSE; }

Oстается только вызывать в подходящее время подпрограммы LockModule и UnlockModule.

Существуют две основные причины, которые должны оставлять DLL сервера загруженной: внешние ссылки на экземпляры объектов и объекты класса, а также невыполненные вызовы IClassFactory::LockServer. Вполне очевидно, как добавить поддержку DllCanUnloadNow в экземпляры и объекты классов. Объекты, расположенные в динамически распределяемой области памяти (такие, как экземпляры классов) просто инкрементируют счетчик блокировок сервера при первом вызове AddRef:

вернуться

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