В примере, показанном в предыдущем разделе, не было точно показано, как и когда должен прекратить работу серверный процесс. В общем случае серверный процесс сам контролирует свое время жизни и может прекратить работу в любой выбранный им момент. Хотя для серверного процесса и допустимо неограниченное время работы, большинство из них предпочитают выключаться, когда не осталось неосвобожденных ссылок на их объекты или объекты класса. Это аналогично стратегии, используемой большинством внутрипроцессных серверов в их реализации
// reasons to remain loaded
// причины оставаться загруженными
LONG g_cLocks = 0;
// called from AddRef + IClassFactory::LockServer(TRUE)
// вызвано из AddRef + IClassFactory::LockServer(TRUE)
void LockModule(void) {
InterlockedIncrement(&g_cLocks);
}
// called from Release + IClassFactory::LockServer(FALSE)
// вызвано из Release + IClassFactory::LockServer(FALSE)
void UnlockModule(void) { InterlockedDecrement(&g_cLocks);
}
Это сделало реализацию
STDAPI DllCanUnloadNow()
{
return g_cLocks ? S_FALSE : S_OK;
}
Подпрограмму
Имеются некоторые различия в том, как ЕХЕ-серверы прекращают работу серверов. Во-первых, обязанностью серверного процесса является упреждающее инициирование процесса своего выключения. В отличие от внутрипроцессных серверов, здесь не существует «сборщика мусора», который запросил бы внепроцессный сервер, желает ли он прекратить работу. Вместо этого серверный процесс должен в подходящий момент явно запустить процесс своего выключения. Если для выключения сервера используется событие Win32 Event, то процесс должен вызвать API-функцию
void UnlockModule(void) {
if (InterlockedDecrement(&g_cLocks) ==0) {
extern HANDLE g_heventShutdown;
SetEvent(g_heventShutdown);
}
}
Если вместо серверного основного потока обслуживается очередь событий
void UnlockModule(void) {
if (InterlockedDecrement(&g_cLocks) == 0) {
extern DWORD g_dwMainThreadID;
// set from main thread
// установлено из основного потока
PostThreadMessage(g_dwMainThreadID, WNLQUIT, 0, 0);
}
}
Если серверный процесс на основе STA знает, что он никогда не будет создавать дополнительные потоки, то он может использовать несколько более простую API-функцию
void UnlockModule(void) {
if (InterlockedDecrement(&g_cLocks) == 0) PostQuitMessage(0);
}
Этот способ работает только при вызове из главного потока серверного процесса.
Второе различие в управлении временем жизни внутрипроцессного и внепроцессного сервера связано с тем, что должно поддерживать сервер в загруженном или работающем состоянии. В случае внутрипроцессного сервера такой силой обладают неосвобожденные ссылки на объекты и неотмененные вызовы
Безусловно, сервер должен оставаться доступным до тех пор, пока внешние клиенты имеют неосвобожденные ссылки на объекты класса сервера. Для внутрипроцессного сервера это реализуется следующим образом:
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 выгружается, несмотря на оставшиеся неосвобожденные ссылки на объекты класса, то даже последующие вызовы метода