проблема с подсчётом ссылок у "клиента"
От: Andy77 Ниоткуда  
Дата: 24.01.02 22:03
Оценка:
имеем два компонента, размещенных на html-странице.
один из них, "клиент", слушает события "сервера".
при подсоединении клиента к серверу вызываем на клиенте
AtlAdvise; сервер, в свою очередь, вызывает AddRef у клиента;
мне же нужно, чтобы при закрытии окна IE убивался вначале
клиент, а потом (после AtlUnadvise) и сервер.
Сейчас никто не убивается, т.к. каждый держит ссылку на товарища

Есть ли какие-то красивые пути?

Спасибо!
Re: проблема с подсчётом ссылок у "клиента"
От: VladD2 Российская Империя www.nemerle.org
Дата: 24.01.02 23:53
Оценка: 3 (1)
Здравствуйте Andy77, Вы писали:

A>имеем два компонента, размещенных на html-странице.

A>один из них, "клиент", слушает события "сервера".
A>при подсоединении клиента к серверу вызываем на клиенте
A>AtlAdvise; сервер, в свою очередь, вызывает AddRef у клиента;
A>мне же нужно, чтобы при закрытии окна IE убивался вначале
A>клиент, а потом (после AtlUnadvise) и сервер.
A>Сейчас никто не убивается, т.к. каждый держит ссылку на товарища

1. Самая красивая развязка такая:

В клиенте создается специальный объект (COM-объект или псевдо-COM-объект).
Псевдо в смысле, что ему понофункциональная реализация не нужна (его время жинни всегда меньше чем время жизни клиента, такая реализация может всегда вазвращать 2 на AddRef и Release, а QI возвращать указатель на this).

Именно к этому объекту и подключается сервер (этот объект передается в Advise).

Общение с родителем ведется через прямой указатель (С++-ый, без поддержки подсчета ссылок). При этом сервер будет AddRef-ить не клиента, а этот объект. Будит ли клиент AddRef-ить этот объект не важно. Далее клиент освобождает ссылку на север и тот отпускает промежуточный объект.


2. Если известен момент смерти клиента (например, у клиента уничтожаетя окно), то в этот момент он должен сделать Unadvise. Сервер осободит ссылку на клиента и тот благополучно скончается.


PS

Вообще IE и ActiveX-ы это не лучший выбор...
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: проблема с подсчётом ссылок у "клиента"
От: Аноним  
Дата: 25.01.02 01:02
Оценка:
VladD2, спасибо за ответ.

VD>Именно к этому объекту и подключается сервер (этот объект передается в Advise).


То есть, этот объект должен получать все события и перенаправлять их клиенту?
Может, где-то есть уже готовый темплейт? :shuffle:

VD>2. Если известен момент смерти клиента (например, у клиента уничтожаетя окно), то в этот момент он должен сделать Unadvise. Сервер осободит ссылку на клиента и тот благополучно скончается.


У меня пока что так и работает, но я нахожу это решение не очень элегантным :(

VD>PS


VD>Вообще IE и ActiveX-ы это не лучший выбор...


Партия сказала "надо", комсомол ответил — "есть!" :)
Re[2]: проблема с подсчётом ссылок у "клиента"
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 25.01.02 13:34
Оценка: 4 (1)
VD>1. Самая красивая развязка такая:

VD>В клиенте создается специальный объект (COM-объект или псевдо-COM-объект).

VD>Псевдо в смысле, что ему понофункциональная реализация не нужна (его время жинни всегда меньше чем время жизни клиента, такая реализация может всегда возвращать 2 на AddRef и Release, а QI возвращать указатель на this).

Не знаю на счет красоты, но когда я писал под Windows Explorer, там такие финты могли привести к краху. Лучше не выпендриваться, а делать нормальный COM объект, поскольку сервер может уведомлять через копию списка уведомлений, а во время рассылки клиент производит отключение. Такое возможно не только в многопоточном приложении.

Я сам пока остановился на следующем решении

  1. пишется полноценная реализация sink-объекта (того которого потом передадим в ConnectionPoint)
  2. к нему пишется класс, который в конструкторе создает этот sink-объект, в деструкторе — отключает sink от уведомлений и уменьшает на него счетчик ссылок.
  3. клиент же должен только подключить sink к уведомлениям и обеспечить синхронность своего разрушения с объектом вспомогательного класса (который автоматически отключит sink)

Коммуникацию между sink и клиентом я предпочитаю делать через __closure (BCB прелести), но можно просто установить в sink-объект указатель на клиента (что бы sink вызывал его методы). В любом случае, вспомогательный объект, при разрушении должен обнулить эти указатели, дабы sink не пытался уведомлять разрушенного клиента.

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

Типа вот.
Лучше не расслабляйся.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[3]: проблема с подсчётом ссылок у "клиента"
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.01.02 18:48
Оценка:
Здравствуйте Коваленко Дмитрий, Вы писали:

КД>Не знаю на счет красоты, но когда я писал под Windows Explorer, там такие финты могли привести к краху.


Если написать прямыми руками, но все будет нормально. Да и IE от других приложений ничем не отличается. Ну, разве что написан прямыми руками. Пример, плиз, в студию.

КД> Лучше не выпендриваться, а делать нормальный COM объект, поскольку сервер может уведомлять через копию списка уведомлений, а во время рассылки клиент производит отключение. Такое возможно не только в многопоточном приложении.


Еще раз говорю. Это пак домыслы. Пример приведи. А мы посмотрим в чем реальная ошибка.

КД>

    КД>
  1. пишется полноценная реализация sink-объекта (того которого потом передадим в ConnectionPoint)
    КД>
  2. к нему пишется класс, который в конструкторе создает этот sink-объект, в деструкторе — отключает sink от уведомлений и уменьшает на него счетчик ссылок.
    КД>
  3. клиент же должен только подключить sink к уведомлениям и обеспечить синхронность своего разрушения с объектом вспомогательного класса (который автоматически отключит sink)

Ну, и нафиг здесь этот полноценный объект? После Unadvise сервер тебя обязан отпустить, а время жизни встроенного объекта как ни крути равно времени жизни клиентского.

КД>Коммуникацию между sink и клиентом я предпочитаю делать через __closure (BCB прелести), но можно просто установить в sink-объект указатель на клиента (что бы sink вызывал его методы).


Класс! Т.е. так как "объект полноценный" ты не имеешь права передавать С++-ный указатель, т.е. тебе нужно будет передавать указатель на интерфейс. Да? Не, все здорово, только вот простой вариант пишется минуты за две, а твой часа за два (опытным программистом).

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


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

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

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


Ну, если использовать критические секции или другую синхронизацию, то все будет ОК, а если нет... ну, тут уж и самые честные COM-объекты не помогут.

КД>Лучше не расслабляйся.


Не. Лучше не напрягаться.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: проблема с подсчётом ссылок у "клиента"
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 28.01.02 08:03
Оценка:
Предварительный просмотр сообщения

проблема с подсчётом ссылок у "клиента"
Здравствуйте VladD2, Вы писали:

VD>Здравствуйте Коваленко Дмитрий, Вы писали:


КД>>Не знаю на счет красоты, но когда я писал под Windows Explorer, там такие финты могли привести к краху.


VD>Если написать прямыми руками, но все будет нормально. Да и IE от других приложений ничем не отличается. Ну, разве что написан прямыми руками. Пример, плиз, в студию.


КД>> Лучше не выпендриваться, а делать нормальный COM объект, поскольку сервер может уведомлять через копию списка уведомлений, а во время рассылки клиент производит отключение. Такое возможно не только в многопоточном приложении.


VD>Еще раз говорю. Это пак домыслы. Пример приведи. А мы посмотрим в чем реальная ошибка.


Привет Влад. Не напрягайся ты так . Я же вроде уже привел нормальный пример. Кто хотел, тот понял, но для тебя еще раз с пояснениями. Рекомендую дочитать до конца и (если очень тянет) написать ответ через 24 часа .

  1. У меня есть (повторяю реально существует) код преднозначенный для нотификации мои-же компонент, работающих в среде Windows Explorer. Ну есть небольшой набор гадостей, о которых в спецификации Shell Namespace благополучно умолчали и для устранения которых пришлось написать как этот код, так и собственный менеджер подгружаемых COM-модулей (типа MyCoCreateInstance(...CLSCTX_INPROC...) ).
  2. Каждый мой компонент (IShellFolder и все такое) при создании регистрируется в моем менеджере уведомлений.
  3. Как ты догадываешься, менеджер должен быть потокобезопасным, поскольку одновременно может регистрироваться несколько компонент из разных окон проводника. Примером такого случая является восстановление ранее открытых окон при входе в систему. На PII-333, как щас помню, эта "параллельность" уже приводила к сбоям кода, хреново защищенного от многопоточности.
Так вот. Как акушер акушеру предложи эффективный вариант асинхронных уведомлений без длительной блокировки менеджера.

Основным способом насилования моего менеджера было открытие нескольких окно (в одном из них) дерево. Становимся в этом дереве на элемент входа в мою ветку и нажимаем "*". В других окнах начинаем модификационные действия, которые производят генерацию уведомлений (приходилось делать пары вызовов — SHNotifyChange(?) и через мой менеджер). Здесь дохнут любые наивные мысли.

С теоритической точки зрения, вызов sink'ов может привести (ну хрен его знает) к попыткам отключения от ConnectionPoint. Тут возможно следующее
1 если это косвенно сделает другой поток, то при заблокированном менеджере получаем самоблокировку. Ну можно конечно изголяться с timeout'ом.
2 Пусть это сделает объект, sink которого в настоящий момент вызвался. Я думаю не надо объяснять, почему лучше всего производить уведомление начиная с хвоста.

КД>>пишется полноценная реализация sink-объекта (того которого потом передадим в ConnectionPoint)

КД>>к нему пишется класс, который в конструкторе создает этот sink-объект, в деструкторе — отключает sink КД>>от уведомлений и уменьшает на него счетчик ссылок. клиент же должен только подключить sink к уведомлениям и обеспечить синхронность своего разрушения с объектом вспомогательного класса (который автоматически отключит sink)


VD>Ну, и нафиг здесь этот полноценный объект? После Unadvise сервер тебя обязан отпустить, а время жизни встроенного объекта как ни крути равно времени жизни клиентского.


Ага. Вот только это ну ни как не вписывается в твои же наставления
Автор: VladD2
Дата: 23.01.02
по поводу отношений между COM объектами.

КД>>Коммуникацию между sink и клиентом я предпочитаю делать через __closure (BCB прелести), но можно просто установить в sink-объект указатель на клиента (что бы sink вызывал его методы).


VD>Класс! Т.е. так как "объект полноценный" ты не имеешь права передавать С++-ный указатель, т.е. тебе нужно будет передавать указатель на интерфейс. Да? Не, все здорово, только вот простой вариант пишется минуты за две, а твой часа за два (опытным программистом).


Ну, мать. Перечисли пять признаков сложных систем из Буча. Один из них и содержит ответ на твой сарказм.

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


VD>Если после Unadvise тебя продолжают уведомлять, то пиши разработчикам этого глюкала, а не замазывай их глюки.


Первым моим серьезным многопоточным приложением было получении уведомлений от сервера баз данных. Там такое происходило на раз. Не вижу причин что бы такое не могло произойти и "домашней" обстановке.

Остальное я вырежу, поскольку ...

Есть очень хорошее правило которое выработано большим числом моих шишок.

  1. все что является точками входа в COM модуль должно быть обернуто try{}catch
  2. все что передается наружу COM-модуля должно осуществлять его блокировку.
  3. во время рассылки уведомлений объект должен заблокировать себя в памяти. Причем если хорошенько подумать, то блокировка должна быть неделегирующего! IUnknonw.
В этих вопросах, согласен с возможным твоим мнением, я превращаюсь в упертого барана, который лучше будет писать страховочный код сравнимый с основным, но избавляющий себя от необходимости сражаться с "непонятными" глюками.

----------
Насчет копирования списка указателей на sink-объекты я могу привести другой случай, когда уведомлений приводит к множественным отключениям.

Мы сейчас занимаемся проектом который управляет данными с достаточно сложной структурой. Одной из самых больным тем редакторов такого рода данных является внутренняя синхронизация, поскольку дублирование данных приводит к искажениям отображаемой информации. То есть объект, представляющий например people, должен разделяться несколькими контейнерами, а не храниться в виде персональных копий в каждом контейнере.

Контейнеры деляться на две категории —
каждый контейнер подключается к уведомлениям своих элементов для того что бы транслировать их через свой "ConnectionPoint"

для реализации такой сложной системы элементы реализуют два счетчика ссылок — всем понятный m_cntRef и специализированный — m_cntForeignRef, который увеличивают/уменьшают контейнеры 1 категории (AddForeignRef()/ReleaseForeignRef())

Обнуление m_cntForeignRef является сигналом контейнерам второй категории освободить ссылки на этот элемент.

Сигнал об обнулении передается через тот же "ConnectionPoint" что и остальные уведомления.

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

Причем описываемый механизм уведомлений идентичен тому, чем ты пытаешься утереть мои сопли — это не COM объекты, а __closure. Поэтому код нотификации выглядит так
void TREGBasePersonData::FireOnChange(TREGEvent event)
{
 //создаем копию списка подписчиков
 TEventSinks Sinks(m_Sinks);

 //блокируем себя в памяти
 TREGBasePersonDataPtr Ptr(this);

 //рассылка уведомлений
 for(TEventSinks::size_type i=0;i!=Sinks.size();++i)
 {
  assert(Sinks[i]!=NULL);

  if(has_element(m_Sinks,Sinks[i]))
   Sinks[i](event,Ptr);
 }
}

Вообщем, Влад, когда пишешь реальный код может происходить такое, от чего волосы стают дыбом. А с тобой ведет (уверен, что интеллегентный) разговор, человек который управляет и отвечает за качество именно такого кода. Мне лично побарабану кто там и как пишет, но понимание и жестокий анализ любого используемого механизма — это неотъемлемая часть создавания исключительно сложных вещей. Не мне тебе объяснять. Для rsdn я (как и ты) это делаю из любви к искусству.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.