Сущность технологии COM

       

Управление жизненным циклом и маршалинг


Ранее в этой главе обсуждались взаимоотношения между администратором заглушек и объектом. Администратор заглушек создается при первом вызове CoMarshalInterface для определенного идентифицированного объекта. Администратор заглушек хранит неосвобожденные ссылки на тот объект, который он предсиавляет, и существует до тех пор, пока остается хотя бы одна неосвобожденная внешняя ссылка на заглушку. Эти внешние ссылки обычно являются заместителями, хотя учитываются и маршалированные объектные ссылки, так как они могут представлять заместители. Когда все внешние ссылки на администратор заглушек уничтожены, он самоуничтожается и освобождает все хранящиеся в нем ссылки на текущий объект. Такое поведение по умолчанию в точности имитирует обычную внутрипроцессную семантику AddRef и Release. Многие объекты не имеют никаких специальных требований относительно жизненного цикла и целиком удовлетворяются этой схемой. Некоторые объекты предпочитают дифференцировать взаимоотношения между внешними ссылками, администратором заглушек и объектом. К счастью, СОМ предоставляет администратору заглушек на время жизненного цикла достаточно приемов работы, которые позволяют реализовывать различные стратегии. Для того чтобы понять, как организован жизненный цикл заглушки, необходимо в первую очередь исследовать алгоритм распределенной сборки мусора (garbage collection) СОМ.

Когда администратор заглушек создан, то идентификатор объекта (OID) регистрируется в распределенном сборщике мусора СОМ, который в настоящее время реализован в службе распознавателя идентификаторов экспортера объектов (OXID Resolver - OR). OR отслеживает, какие идентификаторы объектов экспортируются из каких апартаментов локальной хост-машины. Когда создается администратор заместителей, то CoUnmarshalInterface информирует локальный OR о том, что в апартамент импортируется объектная ссылка. Это означает, что локальный OR также знает, какие OID импортированы в каждый апартамент локальной хост-машины. Если определенный OID импортирован на хост-машину впервые, OR импортирующего хоста устанавливает отношения тестового опроса (ping relationship) с экспортирующим хостом.
Тогда OR импортирующей стороны будет передавать периодические тестовые сообщения через RPC, подтверждая тем самым, что импортирующая хост-машина все еще функционирует и доступна в сети. Текущая реализация посылает такое сообщение один раз в две минуты. Если за последний тестовый интервал (ping interval) не было импортировано никаких дополнительных OID, то посылается простое уведомление. Если же были импортированы новые ссылки или освобождены уже существующие, то посылается более сложное сообщение, показывающее разницу между прошлым и нынешним наборами хранимых ссылок.

В рамках реализации СОМ для Windows NT 4.0 установлено, что если три последовательных тестовых интервала (шесть минут) пройдут без получения уведомления от определенного хоста, то OR будет считать, что хост либо сам вышел из строя, либо недоступен из-за сбоя и сети. В этом случае OR проинформирует всех администраторов заглушек, импортированных ныне отказавшим хостом, что все неосвобожденные ссылки теперь неверны и подлежат освобождению. Если какой-то определенный объект использовался исключительно клиентами ныне мертвого хоста, то в администраторе заглушек более не останется неосвобожденных ссылок и он самоликвидируется, что, в свою очередь, освободит ссылки СОМ на данный объект.

В предыдущем сценарии описывалось, что происходит в том случае, когда хост-машина становится недоступной в сети. Больший интерес представляет сценарий, когда происходит преждевременный выход процесса, в котором остались неосвобожденные заместители. Если процесс закрывается, не вызвав CoUninitialize нужное число раз (например, процесс аварийно завершился), то у библиотеки СОМ нет возможности восстановить утерянные ссылки. Когда это происходит, локальный OR обнаружит гибель процесса и удалит импортированные им ссылки из последующих передаваемых сообщений, что в конце концов заставит OR-экспортера освободить хранящиеся там ссылки. Если в процессе хранились импортированные ссылки на объекты локальной машины, то они могут быть освобождены вскоре после установления смерти клиента.



Распределенный сборщик мусора СОМ иногда критикуют за неэффективность. На самом деле, если объектам нужно надежно установить жизнеспособность клиента, то СОМ сделает это значительно более эффективно, чем модели, специфические для приложения. Дело в том, что сборщик мусора СОМ может агрегировать сохраненные сообщения для всех ссылок на определенной машине в единое периодическое сообщение. Модели же, специфические для приложений, не имеют столь полной информации, и от них можно ожидать единого сообщения для каждого приложения, но не для каждой хост-машины. Для тех сценариев, когда сборщик мусора СОМ действительно влияет на производительность, тестовый опрос для конкретной заглушки может быть отключен с помощью флага MSHLFLAGS_NOPING. Тем не менее, стандартное поведение сборщика мусора пригодно для большинства приложений и превосходит множество специальных моделей, специфических для приложений.

Администратор заглушек следит за тем, сколько внешних ссылок еще не выполнено. Когда заглушка создана, этот счетчик устанавливается в нуль. Если сделан вызов CoMarshalInterface с флагом MSHLFLAGS_NORMAL, этот счетчик увеличивается на некоторое число n, которое записано в маршалированной объектной ссылке. Администратор заместителей, демаршалируя ссылку, добавляет n к своему счетчику хранимых ссылок. Если CoMarshalInterface вызван для администратора заместителей для передачи копии ссылки в другой апартамент, то администратор заместителей может выделить некоторое количество ссылок для инициализации второго заместителя. Если в заместителе осталась только одна ссылка, он должен вернуться в администратор заглушек для запроса дополнительных ссылок.

Часто бывает полезно сохранить маршалированные интерфейсные ссылки в центральной области, доступной для одного или более клиентов. Классическим примером этого является Таблица Исполняемых Объектов (Running Object Table), используемая некоторыми реализациями моникеров. Если бы маршалированные интерфейсные указатели должны были создаваться с использованием флага MSHLFLAGS_NORMAL, то только один клиент смог бы когда-либо демаршалировать объектную ссылку.


Если предполагается, что объектную ссылку будут демаршалировать несколько клиентов, то ссылка должна маршалироваться с применением либо MSHLFLAGS_TABLESTRONG, либо MSHLFLAGS_TABLEWEAK. В обоих случаях маршалированная объектная ссылка может быть демаршалирована несколько раз.

Разница между сильным (strong) и слабым (weak) табличными маршалингами заключается во взаимоотношениях между маршалированой объектной ссылкой и администратором заглушек. Когда маршалированная объектная ссылка создается с флагом MSHLFLAGS_TABLEWEAK, то внешний счетчик ссылок в администраторе заглушек не увеличивается. Это означает, что маршалированная объектная ссылка будет содержать нуль ссылок, и каждому администратору заместителей для получения одной или более внешних ссылок придется связываться с администратором заглушек. Маршалированная с помощью слабой таблицы ссылка не представляет сосчитанную внешнюю ссылку на администратор заглушек. Поэтому, когда последний администратор заместителей отсоединится от администратора заглушек, администратор заглушек самоуничтожится и, конечно, освободит все хранившиеся ссылки СОМ на объект. Если ни один администратор заместителей ни разу не свяжется с администратором заглушек, то последний останется жить в течение неопределенного времени. Отрицательной стороной является то, что неосвобожденная маршалированная объектная ссылка не заставляет оставаться в живых администратор заглушек или объект. Напротив, когда маршалированная объектная ссылка создана с применением флага MSHLFLAGS_TABLESTRONG, то есть с помощью сильной таблицы, то внешний счетчик ссылок увеличивается на единицу. Это означает, что маршалированная объектная ссылка представляет сосчитанную внешнюю ссылку на администратор заглушек. Как и в случае маршалинга по слабой таблице, каждому администратору заместителей понадобится связаться с администратором заглушек, чтобы получить одну или более дополнительных внешних ссылок. Поскольку маршалированная по сильной таблице ссылка представляет внешний счетчик ссылок на администратор заглушек, то при отсоединении последнего администратора заместителей от администратора заглушек он не будет самоуничтожаться и фактически продолжит хранение ссылок СОМ на объект.


Отрицательная сторона маршалинга по сильной таблице заключается в том, что неосвобожденная маршалированная объектная ссылка влияет на жизненный цикл администратора заглушек или объекта. Это означает, что должен существовать какой-нибудь механизм для освобождения ссылок, хранящихся в объектной ссылке, маршалированной по сильной таблице. В СОМ предусмотрена API-функция CoReleaseMarshalData, которая информирует администратор заглушек о том, что маршалированная объектная ссылка уничтожается:

HRESULT CoReleaseMarshalData([in] IStream *pStm);

Подобно CoUnmarshalInterface, CoReleaseMarshalData принимает интерфейсный указатель IStream на маршалированную объектную ссылку. Если таблица маршалинга более не нужна, для ее аннулирования следует вызвать функцию CoReleaseMarshalData. Если по некоторым причинам нормально маршалированная объектная ссылка не будет демаршалироваться с помощью CoUnmarshalInterface, то должна вызываться также функция CoReleaseMarshalData.

Разработчики объектов могут обратиться к счетчику внешних ссылок администратора заглушек вручную, чтобы убедиться в том, что администратор заглушек продолжает жить во время критических фаз жизненного цикла объекта. В СОМ предусмотрена функция CoLockObjectExternal, которая увеличивает или уменьшает на единицу счетчик внешних ссылок администратора заглушек:

HRESULT CoLockObjectExternal([in] IUnknown *pUnkObject, [in] BOOL bLock, [in] BOOL bLastUnlockKillsStub);

Первый параметр CoLockObjectExternal должен указывать на действительный объект, он не может указывать на заместитель. Второй параметр, bLock, показывает, увеличивать или уменьшать на единицу счетчик внешних ссылок администратора заглушек. Третий параметр показывает, нужно или нет уничтожать администратор заглушек, если этот вызов удаляет последнюю внешнюю ссылку. Чтобы понять, для чего необходима функция CoLockObjectExternal, рассмотрим объект, который контролирует некоторое аппаратное устройство и зарегистрирован в таблице исполняемых объектов (Running Object Table) с использованием маршалинга по слабой таблице.


Пока объект осуществляет активный контроль, он хочет быть уверенным, что его администратор заглушек действительно существует, чтобы новые клиенты могли соединяться с объектом для проверки состояния устройства. Если же, однако, объект не осуществляет активный контроль, то он мог бы пожелать, чтобы администратор заглушек исчез, если нет соединенных с ним неосвобожденных заместителей. Для реализации такой функциональной возможности объект должен иметь метод, который начинает осуществлять контроль:

STDMETHODIMP Monitor::StartMonitoring(void) { // ensure that stub manager/object stays alive // убеждаемся, что администратор заглушек/объект остается жив

HRESULT hr = CoLockObjectExternal(this, TRUE, FALSE);

// start hardware monitoring // начинаем контроль за аппаратным устройством

if (SUCCEEDED(hr)) hr = this->EnableHardwareProbe();

return hr; }

а также другой метод, который предписывает объекту закончить активный контроль:

STDMETHODIMP Monitor::StopMonitoring(void) { // stop hardware monitoring // прекращаем контроль за устройством

this->DisableHardwareProbe();

// allow stub manager/object to die when no clients exist // разрешаем администратору заглушек/объекту прекратить // существование, когда нет клиентов

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 // если объект не используется, то выполняем операцию регистрации if (pm->m_cExtRef == 0) pm->TryToLogSampleData(); } return 0; }

Если принять, что метод объекта TryToLogSampleData корректно поддерживает параллелизм, то данная поточная процедура будет регистрировать данные только при условии, что объект не используется внешними клиентами или не осуществляет активный контроль (напомним, что при контроле объект увеличивает счетчик внешних ссылок посредством CoLockObjectExternal).Хотя данный пример может показаться несколько запутанным, имеются случаи, когда отслеживание внешних ссылок является решающим для обеспечения правильности операции. Один классический пример описан в главе 6 и относится к регистрации объектов класса на внепроцессных серверах.

1

В действительности локальный OR ждет в течение короткого промежутка времени, чтобы предоставить шанс демаршализоваться любым оставшимся маршализованным объектным ссылкам, созданным покойным клиентом.


Содержание раздела