Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_6165.html
 

Забавная ошибка при использовании анонимных методов

Опубликовано 16.01.2014 г. 18:07

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

На практике в приложении была реализована такая ситуация: есть набор экранных форм, и менеджер этих форм. Менеджер имеет метод для отображения формы, назовем его TScreenManager.ShowScreen, берет форму и отображает его в некотором внешнем контейнере. Контейнер имеет кнопку для возврата к предыдущему окну. Порядок форм в целом известен, и форма знает куда ей нужно возвращаться, поэтому экспортирует процедуру возврата. Процедура естественно использует все тот же ShowScreen для отображения уже предыдущего окна.
Не все окна могут иметь процедуру возврата, и там где ее нет саму кнопку "назад", показывать тоже не требуется. В итоге получается, что нам одновременно надо узнать что метод доступен, и иметь возможноть его вызывать. Для таких целей нужен указатель на процедуру. То есть вернув указатель мы сразу можем проверить что он не нулевой, и сохранив его в дальнейшем сможем вызывать сам метод. Поскольку мы работаем с классами/обектами, то в данном случае у нас не просто указатель на процедуру, а указатель на метод объекта, то есть в терминологии Delphi - событие (procedure() of object).
Альтернативный вариант - использовать анонимный метод. То есть когда менеджер показывает окно, он вызывает метод получения процедуры возврата, наподобие FBackProc := window.getBackProc. Таким образом ссылка сохраняется, а если вернулся nil, то кнопка Назад скрывается.

Теперь к сути. Для перехода назад, как я уже упомянул, используется тот же метод ShowScreen, который получает ссылку обратной процедуры уже у нового окна, тем самым, замещая в итоге значение текущей ссылки FBackProc. Помня о том, что анонимный метод реализуется в виде интерфейса, то обнуление ссылки приводит к освбождению этого интерфейса. Таким магическим образом во время работы анонимного метода объект его реализующий разрушается до его (метода) завершения.

Для иллюстрации скриншот отладчика во время выполнения кода:

Как видим в начале метода число ссылок на интерфейс равно 1. Далее при выполнении метода TTestMethod.SaveTestMethod() мы открываем новое окно, получаем новую ссылку "Назад" и старая обнуляется. В итоге к концу метода мы получаем уже следующую картину:

при этом число ссылок уже равно нулю, интерфейс "отпустили".

В этом демо-примере (в хе2) сейчас у меня все работает.  В рабочем проекте (ХЕ4) ссылка self->self (интерфейс -> ссылка на объект в котором был создан метод) обнулялась, и значение поля FBool не устанавливалось в true. А в более общем случае должно приводить к AV т.к. пишется неизвестно в чью память.

В итоге получается с одной стороны этой мой косяк, что ссылка на интерфейс была заменена во время работы самого метода. С другой стороны, при "внутреннем" вызове анонимного метода неплохо было бы увеличивать число ссылок, и уменьшать по завершении, чтобы интерфейс гарантированно не разрушился во время выполнения.

 

Метки:  anonymous methods 

Комментарии

Arioch
16.01.2014 в 20:47
QC ?
ter
16.01.2014 в 22:20
I'm not sure that this is a bug.
we should always remember, that anonimous method is implemented as Interface, so nil-ing the reference to method changes methods RefCount to 0 and releases it.
so, just do not change method reference inside method itself :)
Arioch
16.01.2014 в 20:49
captcha error: captcha displays a space between it halves, but does not allow to enter that space
ter
16.01.2014 в 22:43
yep! don't enter that space (:
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно