В предыдущих разделах было показано, как СОМ автоматически загружает 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 из своего клиента может осуществляться значительно дольше, чем можно ожидать. |