void UnlockModule(void) {
if (InterlockedDecrement(&g_cLocks) ==0)
{
extern HANDLE g_heventShutdown;
SetEvent(g_heventShutdown);
}
}
Если вместо серверного основного потока обслуживается очередь событий Windows MSG , то для прерывания цикла обработки сообщений следует использовать некоторые из API-функций. Проще всего использовать PostThreadMessage для передачи в основной поток сообщения WM_QUIT:
void UnlockModule(void) {
if (InterlockedDecrement(&g_cLocks) == 0) {
extern DWORD g_dwMainThreadID;
// set from main thread
// установлено из основного потока
PostThreadMessage(g_dwMainThreadID, WNLQUIT, 0, 0);
}
}
Если серверный процесс на основе STA знает, что он никогда не будет создавать дополнительные потоки, то он может использовать несколько более простую API-функцию PostQuitMessage:
void UnlockModule(void) {
if (InterlockedDecrement(&g_cLocks) == 0) PostQuitMessage(0);
}
Этот способ работает только при вызове из главного потока серверного процесса.
Второе различие в управлении временем жизни внутрипроцессного и внепроцессного сервера связано с тем, что должно поддерживать сервер в загруженном или работающем состоянии. В случае внутрипроцессного сервера такой силой обладают неосвобожденные ссылки на объекты и неотмененные вызовы IClassFactory::LockServer(TRUE). Неосвобожденные ссылки на объекты необходимо рассмотреть в контексте внепроцессного сервера.
Безусловно, сервер должен оставаться доступным до тех пор, пока внешние клиенты имеют неосвобожденные ссылки на объекты класса сервера. Для внутрипроцессного сервера это реализуется следующим образом:
STDMETHODIMP_(ULONG) MyClassObject::AddRef(void) {
LockModule();
// note outstanding reference
// отмечаем неосвобожденную ссылку
return 2;
// non-heap-based object
// объект, размещенный не в «куче»
}
STDMETHODIMP_(ULONG) MyClassObject::Release(void) {
UnlockModule();
// note destroyed reference
// отмечаем уничтоженную ссылку
return 1;
// non-heap-based object
// объект, размещенный не в «куче»
}
Такое поведение является обязательным, поскольку если DLL выгружается, несмотря на оставшиеся неосвобожденные ссылки на объекты класса, то даже последующие вызовы метода Release приведут клиентский процесс к гибели.
К сожалению, предшествующая реализация AddRef и Release не годится для внепроцессных серверов. Напомним, что после входа в апартамент COM первое, что делает типичный внепроцессный сервер, – регистрирует свои объекты класса с помощью библиотеки COM путем вызова CoRegisterClassObject. Тем не менее, пока таблица класса сохраняет объект класса, существует по меньшей мере одна неосвобожденная ссылка COM на объект класса. Это означает, что после регистрации своих объектов класса счетчик блокировок всего модуля будет отличен от нуля. Эти самоустановленные (self-imposed) ссылки не будут освобождены до вызова серверным процессом CoRevokeClassObject. К сожалению, типичный серверный процесс не вызовет CoRevokeClassObject до тех пор, пока счетчик блокировок всего модуля не достигнет нуля, что означает, что серверный процесс никогда не прекратится.
Чтобы прервать циклические отношения между таблицей класса и временем жизни сервера, большинство внепроцессных реализации объектов класса попросту игнорируют неосвобожденные ссылки на AddRef и Release:
STDMETHODIMP_(ULONG) MyClassObject::AddRef(void) {
// ignore outstanding reference
// игнорируем неосвобожденную ссылку
return 2;
// non-heap-based object
// объект, размещенный не в «куче»
}
STDMETHODIMP_(ULONG) MyClassObject::Release(void) {
// ignore destroyed reference
// игнорируем уничтоженную ссылку
return 1;
// non-heap-based object
//объект, размещенный не в «куче»
}
Это означает, что после регистрации объектов своего класса счетчик блокировок всего модуля останется на нуле.
На первый взгляд такая реализация означает, что серверный процесс может прекратить работу, несмотря на то, что существуют неосвобожденные ссылки на объекты его класса. Такое поведение фактически зависит от реализации объекта класса. Напомним, что сервер должен продолжать работу до тех пор, пока на объекты его класса есть внешние ссылки. Предшествующие модификации AddRef и Release влияют только на внутренние ссылки, которые хранятся в таблице классов библиотеки COM и поэтому игнорируются. Когда внешний клиент запрашивает ссылку на один из объектов класса серверного процесса, SCM входит в апартамент объекта класса для отыскания там ссылки на объект класса. В это время делается вызов CoMarshalInterface для сериализации объектной ссылки с целью использования ее клиентом. Если объект класса реализует интерфейс IExternalConnection, то он может заметить, что внешние ссылки являются неосвобожденными, и использовать эти сведения для управления временем жизни сервера. Если предположить, что объект класса реализует интерфейс IExternalConnection, тo следующий код достигает желаемого эффекта:
STDMETHODIMP_(DWORD) MyClassObject::AddConnection(DWORD extconn, DWORD) {
DWORD res = 0;
if (extconn & EXTCONN_STRONG) {
LockModule();
// note external reference
// записываем внешнюю ссылку
res = InterlockedIncrement(&m_cExtRef);
}
return res;
}
STDMETHODIMP_(DWORD) MyClassObject::ReleaseConnection(DWORD extconn, DWORD, BOOL bLastReleaseKillsStub)
{
DWORD res = 0;
if (extconn & EXTCONN_STRONG) {
UnlockModule();
// note external reference
// записываем внешнюю ссылку
res = InterlockedDecrement(&m_cExtRef);
if (res == 0 & bLastReleaseKillsStub)
CoDisconnectObject((IExternalConnection*)this, 0);
}
return res;
}
Отметим, что счетчик блокировок модуля будет ненулевым до тех пор, пока существуют неосвобожденные внешние ссылки на объект класса, в то время как внутренние ссылки, удержанные библиотекой COM, игнорируются.
Хотя технология использования IExternalConnection для объектов класса существовала в COM с самых первых дней, лишь немногие разработчики используют ее на деле. Вместо этого большинство серверов обычно игнорируют неосвобожденные внешние ссылки на объекты класса и завершают серверные процессы преждевременно. Этому положению способствовало присутствие метода LockServer в интерфейсе IClassFactory, который внушает разработчикам мысль, что клиенты будто бы способны в действительности обеспечить выполнение сервера. В то время как большинство разработчиков серверов успешно запирают модуль в методах LockServer, для клиента не существовало надежного способа вызвать данный метод. Рассмотрим следующий клиентский код: IClassFactory *pcf = 0;
HRESULT hr = CoGetClassObject(CLSID_You, CLSCTX_LOCAL_SERVER, О, IID_IClassFactory, (void**)&pcf);
if (SUCCEEDED(hr)) hr = pcf->LockServer(TRUE);
// keep server running?
// поддерживать выполнение сервера?
В первых версиях COM этот фрагмент кода находился бы в условиях серьезной гонки. Отметим, что существует интервал между вызовами CoGetClassObject и IClassFactory::LockServer. В течение этого периода времени другие клиенты могут уничтожить последний остающийся экземпляр класса. Поскольку неосвобожденная ссылка на объект класса игнорируется наивными реализациями серверов, серверный процесс прекратит работу раньше исходного вызова клиентом метода LockServer . Теоретически это можно было бы преодолеть следующим образом: