// разрешаем администратору заглушек/объекту прекратить
// существование, когда нет клиентов
hr = CoLockObjectExternal(this, FALSE, TRUE);
return hr; }
Если принять, что объект был изначально маршалирован с помощью слабой таблицы маршалинга, то данный код обеспечивает жизнь администратора заглушек и объекта до тех пор, пока хотя бы один неосвобожденный заместитель или объект активно контролируют основное устройство.
Кроме предоставления разработчикам объектов возможности устанавливать счетчик внешних ссылок в администраторе заглушек, СОМ также позволяет разработчикам явно уничтожать администратор заглушек, независимо от числа неосвобожденных объектных ссылок. В СОМ предусмотрена API-функция CoDisconnectObject, которая находит администратор заглушек объекта и уничтожает его, отсоединяя все существующие в данный момент заместители:
HRESULT CoDisconnectObject(
[in] Unknown * pUnkObject,
// ptr to object
// указатель на объект
[in] DWORD dwReserved
// reserved, must be zero
// зарезервировано, должно равняться нулю
);
Подобно CoLockObjectExternal, функция CoDisconnectObject должна вызываться из процесса действующего объекта и не может быть вызвана на объект. Для того чтобы применить CoDisconnectObject к показанному выше объекту контроля за устройством, рассмотрим, что произошло бы, если бы состояние объекта было испорчено. Для предотвращения дополнительных вызовов методов объекта, которые могут возвращать ошибочные результаты, объект мог бы вызвать CoDisconnectObject , чтобы резко отсоединить все существующие заместители:
STDMETHODIMP Monitor::GetSample(/*[out]*/ SAMPLEDATA *ps) {
HRESULT hr = this->GetSampleFromProbe(ps);
if (FAILED(hr))
// probe or object may be corrupted
// образец или объект могут быть испорчены
CoDisconnectObject(this, 0);
return hr; }
Функция CoDisconnectObject используется также в случаях, когда процесс хочет отключиться, хотя один или более его объектов могут иметь неосвобожденные заместители. При явном вызове CoDisconnectObject до уничтожения любых объектов, которые могут иметь оставшиеся заместители, нет риска, что исходящие ORPC-запросы будут обслуживаться после того, как объект уже уничтожен. Если бы входящий ORPC-запрос должен был бы поступить после того, как объект уже уничтожен, но администратор заглушек еще жив, то небрежность привела бы к вызову интерфейсной заглушкой соответствующего метода из участка памяти, ранее известного как данный объект. Это вызвало бы лишние неприятности, связанные с тщетными усилиями по отладке.
Обе функции – CoLockObjectExternal и CoDisconnectObject – могут быть использованы разработчиком объектов для манипулирования администратором заглушек. Часто бывает полезно знать, есть ли в администраторе заглушек в наличии какие-либо заместители или объектные ссылки, маршалированные по сильной таблице (strong marshals ). Для информирования объектов о том, что имеются неосвобожденные внешние ссылки на администратор заглушек, в СОМ определен интерфейс IExternalConnection , который может быть экспортирован объектами:
[ uuid(00000019-0000-0000-C000-000000000046), object, local ]
interface IExternalConnection : IUnknown {
DWORD AddConnection(
[in] DWORD extconn,
// type of reference
// тип ссылки
[in] DWORD reserved
// reserved, must be zero
// зарезервировано, должно быть равно нулю
);
DWORD ReleaseConnection(
[in] DWORD extconn,
// type of reference
// тип ссылки
[in] DWORD reserved,
// reserved, must be zero
// зарезервировано, должно быть равно нулю
[in] BOOL fLastReleaseCloses
// should kill stub?
// нужно ли убить заглушку?
); }
При первом подсоединении администратора заглушек к объекту он спрашивает объект, желает ли тот, чтобы его уведомляли о создании или уничтожении внешних ссылок. Он делает это посредством запроса интерфейса IExternalConnection для QueryInterface. Если объект не реализует IExternalConnection, то администратор заглушек будет использовать свой собственный счетчик ссылок при решении вопроса, когда уничтожать администратор заглушек. Если же объект предпочтет реализовать IExternalConnection, то в этом случае администратор заглушек будет жить до тех пор, пока объект явно не уничтожит его путем вызова CoDisconnectObject.
Ожидается, что в объектах, которые реализуют IExternalConnection, поддерживается счетчик блокировок, записывающий число вызовов функций AddConnection и ReleaseConnection. Для большей эффективности СОМ не вызывает AddConnection всякий раз, когда создается заместитель. Это означает, что если объект поддерживает счетчик блокировок, основанный на вызовах функций AddConnection и ReleaseConnection, то этот счетчик блокировок объекта не будет точно отражать число существующих в данный момент объектных ссылок. В то же время СОМ гарантирует, что в том случае, когда счетчик блокировок не равен пулю, существует хотя бы одна неосвобожденная ссылка, а если счетчик блокировок равен нулю, то не существует ни одной неосвобожденной ссылки. Вызовы функции CoLockObjectExternal также будут влиять на этот счетчик. Эта информация особенно полезна для тех объектов, которые заботятся о существовании внешних клиентов. Предположим для примера, что представленный ранее объект для контроля над аппаратным устройством порождает подпроцесс для выполнения фоновой регистрации выборочных данных. Также допустим, что если регистрация произойдет в тот момент, когда объект осуществляет активный контроль данных или, напротив, находится под контролем внешних клиентов, то может возникнуть ошибка выборки. Для предотвращения этой ситуации регистрационный поток мог бы проверять счетчик блокировок, поддерживаемый объектной реализацией IExternalConnection и осуществлять операцию регистрации только тогда, когда не существует внешних ссылок. Это предполагает, что объект реализует IExternalConnection следующим образом:
class Monitor : public IExternalConnection, public IMonitor {
LONG m_cRef;
// normal COM reference count
// обычный счетчик ссылок СОМ
LONG m_cExtRef;
// external reference count
// счетчик внешних ссылок
Monitor(void) : m_cRef(0), m_cExtRef(0) { … }
STDMETHODIMP_(DWORD) AddConnection(DWORD extconn, DWORD) {
if (extconn & EXTCONN_STRONG)
// must check for this bit
// нужно проверить этот бит
return InterlockedIncrement(&m_cExtRef);
}
STDMETHODIMP_(DWORD) ReleaseConnection(DWORD extconn, DWORD,
BOOL bLastUnlockKillsStub) {
DWORD res = 0;
if (extconn & EXTCONN_STRONG) {
// must check for this bit
// нужно проверить этот бит
res = InterlockedDecrement(&m_cExtRef);
if (res == 0 && bLastUnlockKillsStub)
CoDisconnectObject(this, 0);
}
return res;
} }
: : :
: : : }
Получив эту реализацию, подпрограмма потока могла бы проверить состояние объекта и решить, выполнять или нет операцию регистрации, основываясь на уровне активности объекта:
DWORD WINAPI ThreadProc(void *pv) {
// assume ptr to real object is passed to CreateThread
// пусть указатель на действительный объект передается в CreateThread
Monitor *pm = (Monitor*)pv;
while (1) {
// sleep for 10 seconds
// ожидаем 10 секунд
Sleep(1OOOO);
// if object is not in use, perform a log operation