Проблемы с подсчетом ссылок в Delphi 3


Ссылки на интерфейсы, как и ссылки на длинные строки, подсчитываются. Каждый раз, когда вы создаете копию переменной, содержащей интерфейсную ссылку (непосредственным присваиванием или при передаче параметра процедуре), вызывается метод _AddRef объекта, который увеличивает значение счетчика ссылок. При каждом уничтожении ссылки на интерфейс (непосредственным присваиванием или при выходе за пределы области видимости) вызывается метод _Release объекта, который уменьшает значение счетчика ссылок. Когда значение счетчика достигает 0, объект удаляет себя. «Обычные» объектные ссылки никак не влияют на процесс подсчета ссылок.

Данная схема прекрасно работает — если вы взаимодействуете с объектом только с помощью интерфейсных ссылок. Например, для следующего фрагмента:

type IFoo = interface procedure Foo; end; TFoo = class (TObject, IFoo) procedure Foo; end; procedure TFoo.Foo; begin end; prcedure Bar(InterfaceReference: IFoo); begin end; begin Bar(TFoo.Create); end.

ALIGN="JUSTIFY">объект TFoo, созданный вызовом Bar, автоматически уничтожается при выходе из Bar. Но давайте рассмотрим слегка измененный сценарий, в ором интерфейсные ссылки смешиваются со ссылками на объекты:

var ObjectReference: TFoo; begin ObjectReference := TFoo.Create; try Bar(ObjectReference); finally ObjectReference.Free; end; end.

Проблема заключается в том, что присваивание ObjectReference := TFoo.Create не влияет на счетчик ссылок объекта. Свойство RefCount продолжает оставаться равным 0, как и при создании объекта. Тем не менее при вызове процедуры Bar происходит неявное присваивание ее параметру InterfaceReference. При этом генерируется вызов _AddRef, в результате которого RefCount становится равным 1. При выходе из Bar заканчивается область видимости параметра InterfaceReference, поэтому генерируется вызов _Release. В результате RefCount снова обнуляется, что приводит к уничтожению объекта. Ссылка Object Reference становится недействительной! При следующем обращении к ней (в нашем случае — при вызове Free) возникает GPF.

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

В подобных случаях следует принудительно увеличить счетчик ссылок объекта на 1 еще до получения первой интерфейсной ссылки на него. Например, класс TAbstractView из следующего раздела содержит следующий обработчик OnCreate:

procedure TAbstractView.FormCreate (Sender: TObject); begin inherited; _AddRef; // теперь Self можно передавать // в качестве интерфейсной ссылки end;

Явный вызов _AddRef означает, что при создании первой интерфейсной ссылки RefCount увеличится до 2 и в дальнейшем никогда не станет равным 0. Следовательно, объект никогда сам не уничтожится и не разрушит ваших объектных ссылок; он будет жить до тех пор, пока вы не освободите его с помощью Free.

Разумеется, явный вызов _AddRef необходим лишь при смешивании объектных и интерфейсных ссылок. Если вы собираетесь взаимодействовать с объектом только через интерфейсные ссылки, к явным вызовам _AddRef следует относиться с большой осторожностью — вы можете нарушить всю систему подсчета ссылок и ваш объект не будет уничтожаться. И наоборот, при работе с «чисто интерфейсным» объектом никогда не создавайте объектных ссылок на него, иначе они станут недействительными после того, как счетчик интерфейсных ссылок упадет до 0 и объект самоуничтожится. Одна из простейших мер предосторожности состоит в том, чтобы поместить все интерфейсные методы в секцию protected — они останутся доступными через интерфейс, но раз вы не сможете обратиться к ним через объектные ссылки, исчезнет и повод эти ссылки создавать.



- Начало - - Назад - - Вперед -