Интерфейсы в Delphi появились, когда понадобилось поддержать работу с COM и они не очень стройно вписались в язык. В итоге смешивать работу с классами и интерфейсами следует крайне осторожно, всему виной счетчик ссылок, значение которого в классах изначально равно нулю.
В качестве примера форма с одной кнопкой.
При нажатии на кнопку появляются сообщения:
Разберем ход выполнения процедуры Kill:
Способ обойти такую проблему есть - переопределить методы _AddRef и _Release таким образом, чтобы обнуление счетчика ссылок не вызывало освобождение объекта. Но такой шаг увеличивает сложность, т.к. в коде, где часть интерфейсов использует счетчик ссылок, а часть нет, легко запутаться. Тем не менее, в VCL переопределение счетчика ссылок используется. У наследников TComponent счетчик ссылок то есть, то его нет :)
Врядли в языке без сборщика мусора можно было бы реализовать интерфейсы более удобно. Разве что заставить программиста явно вызывать _AddRef и _Release.
В качестве примера форма с одной кнопкой.
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMyClass = class(TInterfacedObject) public destructor Destroy; override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure Kill(Intf: IInterface); end; var Form1: TForm1; implementation {$R *.dfm} { TMyClass } destructor TMyClass.Destroy; begin ShowMessage('TMyClass.Destroy'); inherited; end; procedure TForm1.Button1Click(Sender: TObject); var MyClass: TMyClass; begin MyClass := TMyClass.Create; try Kill(MyClass); finally MyClass.Free; end; end; {$O-} // выключим оптимизатор, чтобы он не выбросил обращение к Intf procedure TForm1.Kill(Intf: IInterface); begin ShowMessage('TMyClass.Kill'); end; {$O+} end.
При нажатии на кнопку появляются сообщения:
- TMyClass.Kill
- TMyClass.Destroy
- TMyClass.Destroy
- Access violation at address 00403BD2 in module 'Project1.exe'. Read of address FFFFFFF8.
Разберем ход выполнения процедуры Kill:
// Изначально Intf.RefCount = 0, это нормальное состояние для TInterfacedObject // Выполняется Intf._AddRef, теперь RefCount = 1 procedure TForm1.Kill(Intf: IInterface); begin ShowMessage('TMyClass.Kill'); // Интерфейс выходит из области видимости, выполняется Intf._Release // И, так как RefCount стал равень нулю, объект уничтожается: TMyClass.Destroy // Это и становится причиной того, что дальше все идет наперекосяк. end;
Способ обойти такую проблему есть - переопределить методы _AddRef и _Release таким образом, чтобы обнуление счетчика ссылок не вызывало освобождение объекта. Но такой шаг увеличивает сложность, т.к. в коде, где часть интерфейсов использует счетчик ссылок, а часть нет, легко запутаться. Тем не менее, в VCL переопределение счетчика ссылок используется. У наследников TComponent счетчик ссылок то есть, то его нет :)
function TComponent._AddRef: Integer; begin if FVCLComObject = nil then Result := -1 // -1 indicates no reference counting is taking place else Result := IVCLComObject(FVCLComObject)._AddRef; end; function TComponent._Release: Integer; begin if FVCLComObject = nil then Result := -1 // -1 indicates no reference counting is taking place else Result := IVCLComObject(FVCLComObject)._Release; end;
Врядли в языке без сборщика мусора можно было бы реализовать интерфейсы более удобно. Разве что заставить программиста явно вызывать _AddRef и _Release.
Комментариев нет:
Отправить комментарий