Сущность технологии СОМ. Библиотека программиста - Дональд Бокс
- Дата:20.06.2024
- Категория: Компьютеры и Интернет / Программирование
- Название: Сущность технологии СОМ. Библиотека программиста
- Автор: Дональд Бокс
- Просмотров:3
- Комментариев:0
Шрифт:
Интервал:
Закладка:
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:
STDMETHODIMP_(ULONG) Chimp::AddRef(void)
{ if (m_cRef == 0) LockModule(); return InterlockedIncrement(&m_cRef); }
и декрементируют счетчик блокировок при заключительном вызове Release:
STDMETHODIMP_(ULONG) Chimp::Release (void)
{ LONG res = InterlockedDecrement(&m_cRef); if (res == 0)
{ delete this; UnlockModule(); }
return res; }
Поскольку объекты, не размещенные в динамически распределяемой области памяти (такие, как объекты классов), не содержат счетчика ссылок, при каждом вызове AddRef и Release нужно инкрементировать или декрементировать счетчик блокировок:
STDMETHODIMP_(ULONG) ChimpClass::AddRef(void) {
LockModule();
return 2;
}
STDMETHODIMP_(ULONG) ChimpClass::Release (void) {
UnlockModule();
return 1;
}
Объекты классов, которые реализуют IClassFactory, должны устанавливать свои серверные счетчики блокировок на вызовы IClassFactory::LockServer:
STDMETHODIMP ChimpClass::LockServer(BOOL bLock)
{
if (bLock) LockModule();
else UnlockModule();
return S_OK;
}
Как будет обсуждаться в главе 6, IClassFactory::LockServer создана в первую очередь для внепроцессных серверов, но она достаточно проста и для использования во внутрипроцессных серверах.
Следует заметить, что в протоколе CoFreeUnusedLibraries/DllCanUnloadNow неотъемлемо присутствует состояние гонки (race condition). Возможно, что один поток задач будет выполнять заключительное освобождение последнего экземпляра, экспортированного из DLL, в то время как второй поток будет выполнять подпрограмму CoFreeUnusedLibraries. В СОМ приняты все меры предосторожности, чтобы избежать этой ситуации. В частности, в реализацию СОМ под Windows NT 4.0 Service Pack 2 добавлена специальная возможность для борьбы с состоянием гонки. Версия Service Pack 2 библиотеки СОМ определяет, чтобы к DLL обращались из нескольких потоков, и вместо того, чтобы незамедлительно выгружать DLL изнутри CoFreeUnusedLibraries, СОМ ставит DLL в очередь DLL, подлежащих освобождению. Затем СОМ будет ждать неопределенное время, пока не разрешит этим неиспользуемым серверным DLL освободиться посредством последующего вызова CoFreeUnusedLibraries, подтверждающего, что никаких остаточных вызовов Release уже не исполняется[1]. Это означает, что в многопоточных средах выгрузка DLL из своего клиента может осуществляться значительно дольше, чем можно ожидать.
Классы и IDL
Как уже отмечалось в начале этой главы, СОМ рассматривает интерфейсы и классы как отдельные сущности. В свете этого классы СОМ (а равно и интерфейсы СОМ) должны быть определены в IDL с целью обеспечить независимое от языка описание конкретных типов данных, которые может экспортировать сервер. IDL-определение класса СОМ содержит список интерфейсов, которые экспортируются элементами класса, исключая катастрофический сбой:
- Аквариум. (Новое издание, исправленное и переработанное) - Виктор Суворов (Резун) - Шпионский детектив
- Права на результаты интеллектуальной деятельности и средства индивидуализации: Комментарий к части четвертой Гражданского кодекса Российской Федерации - Вадим Погуляев - Юриспруденция
- У меня есть идея! Что дальше? - Михаил Соболев - О бизнесе популярно
- Ориентирование - К. М. Станич - Современные любовные романы
- Проект 365 - Константин Рочев - Поэзия