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 . Теоретически это можно было бы преодолеть следующим образом:
IClassFactory *pcf = 0;
HRESULT hr = S_OK;
do {
if (pcf) pcf->Release();
hr = CoGetClassObject(CLSID_You, CLSCTX_LOCAL_SERVER, 0, IID_IClassFactory, (void**)&pcf);
if (FAILED(hr)) break;
hr = pcf->LockServer(TRUE);
// keep server running?
// поддерживать выполнение сервера?
}
while (FAILED(hr));
Отметим, что данный фрагмент кода периодически пытается подсоединиться к объекту класса и заблокировать его, пока вызов LockServer проходит успешно. Если сервер завершит работу преждевременно – между вызовами CoGetClassObject и LockServer, то вызов LockServer возвратит сообщение об ошибке, извещающее об отсоединенном заместителе, что вызовет повтор последовательности. Под Windows NT 3.51 и в более ранних версиях этот нелепый код был единственным надежным способом получения ссылки на объект класса.
Был признан тот факт, что многие реализации серверов не использовали IExternalConnection для должного управления временем жизни сервера, и в версии COM под Windows NT 4.0 введена следующая модернизация для замены этих наивных реализаций. При маршалинге ссылки на объект класса в ответ на вызов CoGetClass0bject SCM вызовет метод объекта класса IClassFactory::LockServer. С тех пор как значительное большинство серверов реализуют IClassFactory в своих объектах класса, эта модернизация исполняемых программ COM исправляет значительное количество дефектов. Однако если объект класса не экспортирует интерфейс IClassFactoryили если сервер должен выполняться и в более ранних версиях COM, чем Windows NT 4.0, то необходимо использовать технологию IExternalConnection.
Следует обсудить еще одну проблему, относящуюся ко времени жизни сервера. Отметим, что когда сервер решает прекратить работу, то он сообщает о том, что главный поток серверного приложения должен начать свою последовательность операций останова (shutdown sequence ) до выхода из процесса. Частью этой последовательности операций останова является вызов CoRevokeClassObject для отмены регистрации его объектов класса. Если, однако, были использованы показанные ранее реализации UnlockModule , то появляются условия серьезной гонки. Возможно, что в промежутке между тем моментом, когда сервер сигнализирует главному потоку посредством вызова SetEvent или PostThreadMessage, и тем моментом, когда сервер аннулирует объекты своего класса, вызывая CoRevokeClassObject, в серверный процесс поступят дополнительные запросы на активацию. Если в этот интервал времени создаются новые объекты, то уже нет способа сообщить главному потоку, что прекращение работы – плохая идея и что у процесса появились новые объекты для обслуживания. Для устранения этих условий гонки в COM предусмотрены две API-функции:
ULONG CoAddRefServerProcess(void);
ULONG CoReleaseServerProcess(void);
Эти две подпрограммы управляют счетчиком блокировок модуля от имени вызывающего объекта. Эти подпрограммы временно блокируют любой доступ к библиотеке COM, чтобы гарантировать, что во время установки счетчика блокировок новые активационные запросы не будут обслуживаться. Кроме того, если функция CoReleaseServerProcess обнаружит, что удаляется последняя блокировка в процессе, то она изнутри пометит все объекты класса в процессе как приостановленные и сообщит SCM, что процесс более не является сервером для его CLSID.